NAV Navbar
Logo
java php go javascript

Introduction

Users keep same passwords everywhere. Despite you following security best practices, a data breach on an unrelated app can compromise your users.

AuthMe helps you do away with passwords. With AuthMe pattern lock, bring UX and security together.

Getting Started

Before you start off, make sure that you have the API Key and Secret Key. If not, get them from here.

They can be found on the left panel of the dashboard.

API Key looks like this: k-162b77f3-5937-4856-af1b-7950a001f732 and can be exposed in public clients (Android, iOS, Windows apps)

Secret Key looks like this: s-001b77f3-5938-0056-af1b-7950a001f712 and should not be put in any public artifact.

Integration has 2 parts.

  1. Client Integration (Android/iOS/Window/Js Integration for web)

  2. Server Integration (PHP/Java/Go)

Android Integration

1. Gradle import

Add the following line to your app build.gradle file

compile 'io.authme:patternlock:0.1.9.6'

Build your project.

2. Config

    Config config = new Config(MainActivity.this);
    config.setEnvironment(Config.SANDBOX); 
    config.setAPIKey("YOUR_API_KEY_HERE"); 

Create a config object.

Set environment to Config.SANDBOX, if you are testing.

Set it to Config.PRODUCTION when you are ready.

3. Call AuthMe

   Intent intent = new Intent(MainActivity.this, AuthScreen.class);
   intent.putExtra("email", "USER_EMAIL_ID");
   startActivityForResult(intent, RESULT);

Call AuthScreen activity wherever you are signing up or signing in the user.

If the use has set the patter already, AuthMe will offer a trust score to you in the callback.

If not, AuthMe will help the user set the pattern.

4. Callback

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  switch (requestCode) {
    case RESULT : {
        switch (resultCode) {
            case Config.SIGNUP_PATTERN : {
                Toast.makeText(getApplicationContext(), "Sign up successfull", Toast.LENGTH_LONG)
                        .show();
                 } break;

            case Config.LOGIN_PATTERN : {
                Toast.makeText(getApplicationContext(), data.getStringExtra("response"), Toast.LENGTH_LONG)
                        .show(); //you will get a trust score in the response here.
                } break;

            case Config.RESET_PATTERN: {
                Toast.makeText(getApplicationContext(), "Reset Pattern", Toast.LENGTH_LONG)
                        .show();
                } break;            

            case Config.RESULT_FAILED : {
                Toast.makeText(getApplicationContext(), "Failed To Identify", Toast.LENGTH_LONG)
                        .show();
                if (data.hasExtra("response")) {
                            Toast.makeText(getApplicationContext(), data.getStringExtra("response"), Toast.LENGTH_LONG)
                                    .show();
                        }
                } break;

                default: break;
                }

            } break;

            default: break;

      }
    }

You will get the callback in onActivityResult.

a. Sign up successfull

In case the user didn’t have a pattern set earlier, we sign up the user.

Check for case Config.SIGNUP_PATTERN in onActivityResult.

b. Login Trust Score

In case the user swiped to login, we calculate the trust score and give it back to you.

Check for case Config.LOGIN_PATTERN in onActivityResult.

c. Reset Pattern

In case the user already exists on the platform, but forgot the pattern, we redirect you to reset the pattern.

Check for case Config.RESET_PATTERN in onActivityResult.

d. Failed to Identify

In case the user failed to identify with AuthMe.

Check for case Config.RESULT_FAILED in onActivityResult.

The code snippet on the right side shows how to handle these cases.

5. Trust Score

{
"Accept":true,
"Id":0,
"Reason":"",
"hash":"0352044c22fac6455b46de681701a733f86bca1f8477bf015d3ebd9817e2b9dc",
"Motion":83,
"Path":91,
"Speed":72
}

In case the user tried to login, we send the trust score which looks as follows:

What’s a trust score?

Trust score is the indication of how much the user matches to his behavioural profile.

Path score 90 means that user matches 90% to his signature behaviour.

Speed score 72 means that user matches 72% to his velocity behaviour.

Motion sensor 93 means that user matches 93% to his movements behaviour.

What’s hash? Why is it so important?

You are expected to process the trust score on the server and implement the business logic on the server.

When you get a trust score in the LOGIN_PATTERN case above, post this on your server and calculate the hash to verify if the trust score is not tampered with.

In the server integration, we will demonstrate how a hash is calculated.

Web Integration

AuthMe web helps you identify the user based on device fingerprint + typing behaviour.

If the user exists on AuthMe network, you can identify the user based on just email or mobile number.

1. Include the js

<head>
  <title>AuthMe - Web Integration</title>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.0/jquery.min.js"></script>
  <script src="https://api.authme.authme.host/typer.js"></script>
  <script type="text/javascript">
    function initAuthMe() {
      window.am = new AuthmeTyper();
      AuthmeTyper.prototype.information = [];
          AuthmeTyper.prototype.startListener();
    }
  </script>
</head>

As shown in the js code snippet on the right, include the typer.js in your html page.

Also, copy the initAuthMe() function call.

2. Create a login form

<form action="#" id="loginform" onsubmit="return false">
  <input type="text" name="logininput" placeholder="Email or Mobile">
  <input type="submit" value="login">
</form>

This login form can capture any user identifier.

It could be either email id, mobile number or some primary key.

Longer the length of identifier, better the trust score.

3. Get the score

<script>
$("#loginform" ).submit(function( event ) {

  var data = window.am.information;

  initAuthMe();

  $.ajax({
          url: 'https://api.authme.authme.host/typing/check',
          dataType: 'json',
          data: JSON.stringify({
            UserIdentifier: "email",
            Data: data
          }),
          headers: {
            'X-Api-Key': "<YOUR_API_KEY>",
          },
          method: "POST",
          success: function (response) {
            var json = JSON.stringify(response.Data);
          }
        })

});
</script>

Copy this script into your HTML code.

This snippet gets executed when the user submits the form.

If user with the email/phone exists on the AuthMe network, you will get a trust score.

If not, you need to continue with your existing OTP/password flow.

4. What’s a trust score?

{
"Accept":true,
"Count":6,
"Hash":"378dc964fccccfa65c5ea5f5dfc95655eb46679b57d934898fb6342f0add75eb",
"Input-0-Name":"text#logininput",
"Input-0-Score":93,
"Score":93,
"UserProfilePercent":100
}

Trust score (see javascript section on the right) is an indication of how much the user matches with his/her typing behaviour.

You can have several inputs in the form such as <input type="text" name="logininput" placeholder="Email or Mobile">

You will receive the respective trust scores in the Input--Name and Input-Score fields.

In the form mentioned above, there’s only one input with name logininput.

Hence the trust score has Input-0-Name and its score respectively.

var json will have the trust score.

5. What to do with the trust score?

Post this trust score to your server and calculate the hash to confirm that the score is not tampered with.

If the trust score is above 80%, you can log in the user.

If not, continue with the existing flow (OTP or passwords.)

In the server integration, we will demonstrate how a hash is calculated.

Server Integration

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;


class HashUtils {

  private static final String SEPARATOR = "|";
  private static final String HASH_KEY = "hash";
  private static final String SHA_256 = "SHA-256";

  public static void main(String[] args) throws java.lang.Exception {
    Map<String, Object> re = new HashMap<String, Object>();
    re.put("test", "hello");
    System.out.println(generateHash(re, "YOUR_API_KEY", "YOUR_SECRET_KEY"));
  }

  public static String generateHash(Map<String, Object> requestMap, String apiKey, String apiSecret) throws NoSuchAlgorithmException {
    String[] keys = requestMap.keySet().toArray(new String[]{});
    Arrays.sort(keys);
    StringBuilder hashString = new StringBuilder(apiKey);

    for (String key : keys) {
      if (HASH_KEY.equalsIgnoreCase(key)) {
        continue;
      }
      Object value = requestMap.get(key);
      hashString.append(SEPARATOR).append(String.valueOf(value));
    }

    hashString.append(SEPARATOR).append(apiSecret);

    MessageDigest digest = MessageDigest.getInstance(SHA_256);
    byte[] hash = digest.digest(hashString.toString().getBytes());
    return bytesToHex(hash);


  }

  private static String bytesToHex(byte[] bytes) {
    StringBuilder result = new StringBuilder();
    for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
    return result.toString();
  }

}
<?php
namespace authme;
class AuthmeClient
{
    public static function generate_hash($params, $apiKey, $apiSecret)
    {
        $str = self::flatten_array($params);
        $str .= $apiSecret;
        $str = $apiKey . "|" . $str;
        print $str;
        return hash("sha256", $str);
    }
    private static function flatten_array($data)
    {
        if (empty($data)) {
            return "|";
        }
        $str = "";
        ksort($data);
        foreach ($data as $key => $value) {
            if ($key === "Hash") {
                continue;
            } elseif (is_array($value)) {
                $str .= self::flatten_array($value);
            } elseif (is_null($value)) {
                $str .= "null" . "|";
            } elseif (is_bool($value)) {
                $bool_str = $value ? "true" : "false";
                $str .= $bool_str . "|";
            } else {
                $str .= $value . "|";
            }
        }
        return $str;
    }
}

$request = array();
$request["test"] = "hello";
$hash = AuthmeClient::generate_hash($request, "YOUR_API_KEY", "YOUR_SECRET_KEY");

if ($hash != "292115573432d504b797a836e3a1936ea5ce9ef61bd90c8096dbd86c449d3d75") {
    print "Hash is $hash and not 292115573432d504b797a836e3a1936ea5ce9ef61bd90c8096dbd86c449d3d75";
    throw new \Exception("hash mismatch");
}

print "Hash: $hash";
package authmeclient

import (
  "time"
  "net/http"
  "encoding/json"
  "io/ioutil"
  "bytes"
  "errors"
  "sort"
  "fmt"
  "strings"
  "crypto/sha256"
)

var client = &http.Client{}

type AuthmeClientInterface interface {
  GetOrder(referenceId string) (Order, error)
  InitOrder(AuthenticationRequest) (Order, error)
  InitOrderWithApiKey(orderPlace AuthenticationRequest, apiKey string) (Order, error)
}

type Order struct {
  Id          uint64 `json:"-"`
  CreatedAt   time.Time
  ReferenceId string
  UserId      uint64
  Comment     string
  Status      string
  Details     string
  Token       *string
  Data        *string
}

type AuthmeClient struct {
  endPoint  string
  apiKey    string
  apiSecret string
}

type AuthenticationRequest struct {
  ReferenceId        string
  UserIdentifier     string
  UserIdentifierType string
  PublicKeyJson      string
  Comment            string
  Message            string
  Ip                 string
  Hash               string
  Client             string
  Data               string
}

func NewAuthmeClient(endPoint string) AuthmeClientInterface {
  return &AuthmeClient{
    endPoint: endPoint,
  }
}

func NewAuthmeClientWithApiKey(endPoint string, apiKey string, apiSecret string) AuthmeClientInterface {
  return &AuthmeClient{
    endPoint: endPoint,
    apiKey: apiKey,
    apiSecret: apiSecret,
  }
}

func (x *AuthmeClient) GenerateHash(request map[string]interface{}) string {
  var keys []string
  //log.Infof("%v", x)
  for k := range request {
    if k == "Hash" {
      continue
    }
    keys = append(keys, k)
  }

  //givenHash, ok := request["Hash"]
  //if !ok {
  //    return false
  //}
  sort.Strings(keys)
  values := make([]string, 1)
  values[0] = x.apiKey

  for _, key := range keys {
    var v string
    if request[key] != nil {
      v = fmt.Sprintf("%v", request[key])
    } else {
      v = ""
    }
    if len(v) < 1 {
      //values = append(values, "")
      continue
    }
    //log.Infof("%s: %v", key, v)
    values = append(values, v)
  }
  values = append(values, x.apiSecret)

  hashString := strings.Join(values, "|")
  h := sha256.New()
  h.Write([]byte(hashString))
  bs := h.Sum(nil)
  calculatedHash := fmt.Sprintf("%x", bs)
  return calculatedHash
}

func (ac *AuthmeClient) GetOrder(referenceId  string) (Order, error) {

  url := ac.endPoint + "/order/" + referenceId
  response, err := http.Get(url)
  if err != nil {
    return Order{}, err
  }
  content, err := ioutil.ReadAll(response.Body)
  var order Order
  json.Unmarshal(content, &order)
  return order, err
}

func (ac *AuthmeClient) InitOrder(order AuthenticationRequest) (Order, error) {
  if len(ac.apiKey) < 2 {
    panic(errors.New("ApiKey not set"))
  }
  return ac.InitOrderWithApiKey(order, ac.apiKey)
}

func (ac *AuthmeClient) InitOrderWithApiKey(orderPlace AuthenticationRequest, apiKey string) (Order, error) {
  jsonRequest, _ := json.Marshal(orderPlace)
  req, err := http.NewRequest("POST", ac.endPoint + "/order", bytes.NewBuffer(jsonRequest))
  req.Header.Set("Content-Type", "application/json")
  req.Header.Set("X-API-KEY", apiKey)

  resp, err := client.Do(req)

  if err != nil {
    return Order{}, err
  }
  body, err := ioutil.ReadAll(resp.Body)
  var o Order
  json.Unmarshal([]byte(body), &o)
  return o, nil
}


/*------------------------------------------------------------------------------------------------------------*/
package authmeclient

import "testing"

func TestHashCalculation(t *testing.T) {

  ac := AuthmeClient{
    apiKey: "YOUR_API_KEY",
    apiSecret: "YOUR_SECRET_KEY",
  }

  request := make(map[string]interface{})
  request["test"] = "hello"

  calculatedHash := ac.GenerateHash(request)
  t.Logf("Calculated hash: %s", calculatedHash)
  if calculatedHash != "292115573432d504b797a836e3a1936ea5ce9ef61bd90c8096dbd86c449d3d75" {
    t.Fail()
    t.Error("Failed, expected hash 292115573432d504b797a836e3a1936ea5ce9ef61bd90c8096dbd86c449d3d75")
  }

}

Here’s the logic to calculate hash.

Modules on the right side, should help you integrate this quickly.

Click on PHP, Java or other tabs as relevant to your server technology.

Add Ons

How do I put my logo on the AuthMe Screen?

String fileName = "mylogo";
Bitmap bitmap = BitmapFactory.decodeResource(MainActivity.this.getResources(), R.drawable.nestaway_bird_logo);
        try {
            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
            FileOutputStream fo = openFileOutput(fileName, Context.MODE_PRIVATE); // MODE_PRIAVE so that no one else can access this file
            fo.write(bytes.toByteArray());
            fo.close();
        } catch (Exception e) {
            e.printStackTrace();
            fileName = null;
        }
intent.putExtra("logo", fileName);
startActivityForResult(intent, RESULT);

a. Upload your logo in the drawable section of your app.

b. Create a bitmap of your logo and load it into a file accessible only to your app.

c. Add the filename in the intent before calling startActivityForResult(intent, RESULT)

How do I change the color of statusbar?

intent.putExtra("statusbar", "#443FFF");
startActivityForResult(intent, RESULT);

Add the intent extra ‘statusbar’ before calling startActivityForResult(intent, RESULT)

How do I change the color of titlebar?

intent.putExtra("titlecolor", "#443FFF");
startActivityForResult(intent, RESULT);

Add the intent extra ‘titlecolor’ before calling startActivityForResult(intent, RESULT)

How do I change the titlebar text?

intent.putExtra("titletext", "Authentication Screen");
startActivityForResult(intent, RESULT);

Add the intent extra ‘titletext’ before calling startActivityForResult(intent, RESULT)

Errors

The Kittn API uses the following error codes:

Error Code Meaning
400 Bad Request – Your request sucks
401 Unauthorized – Your API key is wrong
403 Forbidden – The kitten requested is hidden for administrators only
404 Not Found – The specified kitten could not be found
405 Method Not Allowed – You tried to access a kitten with an invalid method
406 Not Acceptable – You requested a format that isn’t json
410 Gone – The kitten requested has been removed from our servers
418 I’m a teapot
429 Too Many Requests – You’re requesting too many kittens! Slow down!
500 Internal Server Error – We had a problem with our server. Try again later.
503 Service Unavailable – We’re temporarily offline for maintenance. Please try again later.