Using Auth0 and Arkose for New Account Registration

Overview

This is a high level overview of how to integrate Arkose Labs Fraud Deterrence Platform directly via the Auth0 development console. There are two basic points of integration:

  1. The Client API
  2. The server side token verification API (Verify API)

The client API is simply javascript that is being added to the registration page where Auth0 is implemented for sign-up, the output of which is an Arkose token.  And the server side token verification API is the verification of that token (output of client API) via the Arkose verification endpoint.


Integrating the Client API

Using the Auth0 “Branding” feature Universal Login you can modify the sign-up page HTML and add the javascript tag to load the Arkose client API.  You also add the javascript functions for configuring and running the client API and passing the token to the Auth0 server. For more information on setting up the Client API please go to: Client-Side Instructions.

Invoke Challenge on Page Load

The below snippets show both ways to invoke the challenge, on page load or on button press. The demo Key is used to invoke challenge every time.

<!DOCTYPE html>
<html>
<head>
 <!--
    Include the Arkose Labs API in the <head> of your page. In the example below, remember to
    replace <company> with your company's personalized Client API URL name, and replace <YOUR PUBLIC KEY> with the public key supplied to you by Arkose Labs.
    e.g. <script src="//client-api.arkoselabs.com/v2/11111111-1111-1111-1111-111111111111/api.js" data-callback="setupEnforcement" async defer></script>
  -->
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>Sign In with Auth0</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
  <script src="https://<company>-api.arkoselabs.com/v2/<YOUR PUBLIC KEY>/api.js" data-callback="setupEnforcement" async defer></script>
</head>
<body>

  <!--[if IE 8]>
  <script src="//cdnjs.cloudflare.com/ajax/libs/ie8/0.2.5/ie8.js"></script>
  <![endif]-->

  <!--[if lte IE 9]>
  <script src="https://cdn.auth0.com/js/base64.js"></script>
  <script src="https://cdn.auth0.com/js/es5-shim.min.js"></script>
  <![endif]-->

  <script src="https://cdn.auth0.com/js/lock/11.32/lock.min.js"></script>
  <script>
    // Decode utf8 characters properly
    var config = JSON.parse(decodeURIComponent(escape(window.atob('@@[email protected]@'))));
    config.extraParams = config.extraParams || {};
    var connection = config.connection;
    var prompt = config.prompt;
    var languageDictionary;
    var language;

    if (config.dict && config.dict.signin && config.dict.signin.title) {
      languageDictionary = { title: config.dict.signin.title };
    } else if (typeof config.dict === 'string') {
      language = config.dict;
    }
    var loginHint = config.extraParams.login_hint;
    var colors = config.colors || {};
    
    function setupEnforcement(myEnforcement) {
      console.log('setupenforcement');
      myEnforcement.setConfig({
        data: '',
        language: 'en',
        onReady: function(response) {
          myEnforcement.run();
          console.log(config);
        },
        
       onCompleted: function(response) {
        console.log(response.token);
        config.internalOptions['arkoseToken'] = response.token;
        
     // Available Lock configuration options: https://auth0.com/docs/libraries/lock/v11/configuration
    var lock = new Auth0Lock(config.clientID, config.auth0Domain, {
     
      auth: {
        redirectUrl: config.callbackURL,
        responseType: (config.internalOptions || {}).response_type ||
          (config.callbackOnLocationHash ? 'token' : 'code'),
        params: config.internalOptions
      },
      configurationBaseUrl: config.clientConfigurationBaseUrl,
      overrides: {
        __tenant: config.auth0Tenant,
        __token_issuer: config.authorizationServer.issuer
      },
      assetsUrl:  config.assetsUrl,
      allowedConnections: connection ? [connection] : null,
      rememberLastLogin: !prompt,
      language: language,
      languageBaseUrl: config.languageBaseUrl,
      languageDictionary: languageDictionary,
      theme: {
        //logo:            'YOUR LOGO HERE',
        primaryColor:    colors.primary ? colors.primary : 'green'
      },
      prefill: loginHint ? { email: loginHint, username: loginHint } : null,
      closable: false,
      defaultADUsernameFromEmailPrefix: false,
     
  additionalSignUpFields: [{
    type: "hidden",
    name: "arkoseToken",
    value: response.token
  }]
    });
         
    if(colors.page_background) {
      var css = '.auth0-lock.auth0-lock .auth0-lock-overlay { background: ' +
                  colors.page_background +
                ' }';
      var style = document.createElement('style');
      style.appendChild(document.createTextNode(css));
      document.body.appendChild(style);
    }
    lock.show();
       }     
     });
  }
  </script>
</body>
</html>  

Invoke Challenge on Button Press

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>Sign In with Auth0</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <script src="https://client-api.arkoselabs.com/v2/11111111-1111-1111-1111-111111111111/api.js" data-callback="setupEnforcement" async defer></script>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
</head>
  <style>
    body, html {
      height: 100%;
      background-color: #f9f9f9;
    }

    .login-container {
      position: relative;
      height: 100%;
    }

    .login-box {
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
      padding: 15px;
      background-color: #fff;
      box-shadow: 0px 5px 5px #ccc;
      border-radius: 5px;
      border-top: 1px solid #e9e9e9;
    }

    .login-header { 
      text-align: center;
    }

    .login-header img {
      width: 75px;
    }

    #error-message {
      display: none;
      white-space: break-spaces;
    }
  </style>
<body>
  <div class="login-container">
    <div class="col-xs-12 col-sm-4 col-sm-offset-4 login-box">
      <div class="login-header">
        <img src="https://cdn.auth0.com/styleguide/1.0.0/img/badge.svg"/>
        <h3>Welcome</h3>
        <h5>PLEASE REGISTER</h5>
      </div>
      <div id="error-message" class="alert alert-danger"></div>
      <form onsubmit="return false;" method="post">
        <div class="form-group">
         <label for="name">Email</label>
          <input
            type="email"
            class="form-control"
            id="email"
            placeholder="Enter your email">
        </div>
        <div class="form-group">
         <label for="firstname">First Name</label>
          <input
            type="text"
            class="form-control"
            id="firstname"
            placeholder="Enter your first name">
        </div>
        <div class="form-group">
         <label for="familyname">Family Name</label>
          <input
            type="text"
            class="form-control"
            id="familyname"
            placeholder="Enter your family name">
        </div>
        <div class="form-group">
         <label for="password">Password</label>
          <input
            type="password"
            class="form-control"
            id="password"
            placeholder="Enter your password">
        </div>

        <div class="captcha-container form-group"></div>
        <div id="arkoseChallenge"></div>
        <button
                type="submit"
                id="btn-login"
                class="btn btn-primary btn-block">
          Register
        </button>  
      </form>
    </div>
  </div>

  <!--[if IE 8]>
  <script src="//cdnjs.cloudflare.com/ajax/libs/ie8/0.2.5/ie8.js"></script>
  <![endif]-->

  <!--[if lte IE 9]>
  <script src="https://cdn.auth0.com/js/polyfills/1.0/base64.min.js"></script>
  <script src="https://cdn.auth0.com/js/polyfills/1.0/es5-shim.min.js"></script>
  <![endif]-->

  <script src="https://cdn.auth0.com/js/auth0/9.18/auth0.min.js"></script>
  <script src="https://cdn.auth0.com/js/polyfills/1.0/object-assign.min.js"></script>
  <script>
    window.addEventListener('load', function() {

      var config = JSON.parse(
        decodeURIComponent(escape(window.atob('@@[email protected]@')))
      );

      var leeway = config.internalOptions.leeway;
      if (leeway) {
        var convertedLeeway = parseInt(leeway);
      
        if (!isNaN(convertedLeeway)) {
          config.internalOptions.leeway = convertedLeeway;
        }
      }

      var params = Object.assign({
        overrides: {
          __tenant: config.auth0Tenant,
          __token_issuer: config.authorizationServer.issuer
        },
        domain: config.auth0Domain,
        clientID: config.clientID,
        redirectUri: config.callbackURL,
        responseType: 'code'
      }, config.internalOptions);

      
      var databaseConnection = 'Username-Password-Authentication';
      
        
       function setupEnforcement(myEnforcement) {
         console.log('setupenforcement');
        myEnforcement.setConfig({
          data: 'passedData',
          language: 'en',
          onCompleted: function(response) {
            console.log(response.token);
            var email = document.getElementById('email').value;
            var password = document.getElementById('password').value;
            var firstname = document.getElementById('firstname').value;
            var familyname = document.getElementById('familyname').value;
            var tokenSplit = response.token.split ("|");
            var verifyToken = tokenSplit[0]+"|"+tokenSplit[1];
            console.log(verifyToken);
           // config.internalOptions['arkoseToken'] = response.token;
            // button.disabled = true;
            var webAuth = new auth0.WebAuth(params);
            //var captcha = webAuth.renderCaptcha(
            //  document.querySelector('.captcha-container')
            //);
            webAuth.signup({
              connection: databaseConnection,
              email: email,
              password: password,
              user_metadata: { arkoseToken: verifyToken}
            },
            function (err) {
            if (err) return alert(err.policy || err.description);
            return alert('Success signup!')
        });
          },
                           
          selector: '#btn-login',
        });
      }
      
      window.setupEnforcement = setupEnforcement;

      function login(e) {
        e.preventDefault();
        var button = this;
        
      }
      
      document.getElementById('btn-login').addEventListener('click', login);
    });
  </script>
</body>
</html>

Passing the Token

config.internalOptions['arkoseToken'] = response.token; is responsible to set the Arkose Token as part of the Registration form, as seen below:

"arkose_token": "76762e7ba389f15a6.6902851603|r=ap-southeast-2|metabgclr=%23ffffff|guitextcolor=%23000000|metaicon"
"protocol":"oauth2"

Integrating the Verify API

We leverage an Auth0 “Action” Flow to create the pre-registration action flow. This action flow is configured to verify the token provided by Arkose. The action flow evaluates if the token is valid and solved is true, then we allow the user to move forward. For more information on setting up the Verify API please go to: Server-Side Instructions.

Below is an example of the flow:

Custom Action - Arkose Labs Verify

A custom action can be written in Auth0 and attached to the Registration Flow. This helps in validating the Arkose Session token that is passed from the client end. Please update the name in this action depending on whether the token is passed as captcha or Arkose Token or other: event.user.user_metadata.<variable>.

/**
* This flow is to protect the Auth0 registration flow from a bot attack using Arkose Labs.
* This is the second part of the integration -server side verification of the token.
* The first part is executed on the client side to request the token from Arkose as part of the Detect phase.
* This flow assumed that a registration value was provided for the Arkose Token in a variable called "arkoseToken".
* This flow will terminate a registration in the event of a null token or invalid token.
*/
exports.onExecutePreUserRegistration   = async (event, api) => {
  const axios = require('axios');

  //If the Arkose session token is missing the deny access
  if (!event.user.user_metadata.arkoseToken) {
    api.access.deny('Missing Arkose Session Token!');
  } else {
    // Call the Arkose Verify API, passing in the private key and the session_token
    try {
      const response = await axios.post(
        'https://verify-api.arkoselabs.com/api/v4/verify/', 
        {
          private_key: event.secrets.PRIVATE_KEY,
          session_token: event.user.user_metadata.arkoseToken
        }
      );

      // If the response from the Arkose verify call is false, then deny access
      if (response.data.session_details.solved != true) {
        api.access.deny("Arkose Invalid Token");
      }

    } catch (error) {
      // A 400 is returned from Arkose if the private key and token do not match, so 
      // catch that and deny access
      if (error.response.status == 400) {
        api.access.deny("Arkose Access Denied!");
      } else {
        // If anything else goes wrong then fail closed and respond with the error
        // receieved
        api.access.deny(error.response.data);
      }
    }
  }
}

Summary

Arkose Labs can be deployed rapidly via the Auth0 development console to quickly enable and protect your pre-registration flow.  We would be happy to demonstrate this as a next step.