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.
Client Integration (Android/iOS/Window/Js Integration for web)
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-
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. |