React or Angular Setup Guide

Overview

This page describes how to use the Arkose Fraud Detection Platform (Arkose Platform) JavaScript API with single page applications (SPA) built using frameworks such as React and Angular.

Prerequisite: API Request Authentication Private/Public Key Pair

Arkose Labs authenticates your API requests using a private/public key pair retrievable from the Arkose Labs Command Center. To get the key pair, go to the left menubar’s Settings entry and then to its Keys sub-entry as shown below. If you do not have access to the Command Center or do not have your private and public keys, contact your Arkose Sales Rep or Solution Consultant.

You need the private key to authenticate when using the Arkose Verify API. This private key must NOT be published on a client facing website, and must only be used on your Verify API server-side implementation.

Loading the API

🚧

Using Multiple Public Keys

Important: Once the Arkose Platform API is loaded within the DOM, you cannot unload that API and use a different public key. If you need to support different protected flows within the SPA with multiple public keys, see the Multi-Public key section on this page.

Your SPA loads the Arkose Platform API via a <script> tag. It contains:

  • The Arkose Platform API’s URL.

  • Your public key from the Arkose Labs Command Center

  • As the value of data-callback, the name of a JavaScript function that configures the Arkose Platform client API.

Full details about the script tag and function are in the Client-Side Instructions.

For Arkose Protect:
<script src="https://client-api.arkoselabs.com/v2/<YOUR_PUBLIC_KEY>/api.js" data-callback="setupEnforcement" async defer/>

For Arkose Detect:
<script src="https://client-api.arkoselabs.com/v2/<YOUR_PUBLIC_KEY>/api.js" data-callback="setupDetect" async defer/>

React Example

Setup Function

First, somewhere in your React application, you need to write the global JavaScript function that configures the API. This is the function named in the above <script> example's data-callback attribute.

Note that the final line in this example creates a global reference to the setup function. Without this reference, the Arkose Platform API won’t be able to find the function and complete loading.

Please use the appropriate code view for the product you are using , either Arkose Protect or Arkose Detect.

// Create the setup function the Arkose Labs API uses to configure its use
  // and the callbacks that it triggers
  function setupEnforcement(enforcementObject) {
    window.Arkose = enforcementObject;
    window.Arkose.setConfig({
      selector: '#arkose-ec',
      onReady: () => {},
      onShown: () => {},
      onShow: () => {},
      onSuppress: () => {},
      onCompleted: (response) => {},
      onResize: (response) => {},
      onReset: () => {},
      onHide: () => {},
      onError: (response) => {},
      onFailed: (response) => {},
    });
  }

  // Make the setup function a global variable so once the Arkose Labs API 
  // is loaded, it can run it. This variable's name MUST match the setup function's 
  // name as defined in the Arkose Labs component 'data-callback' attribute
  window.setupEnforcement = setupEnforcement;
// Create the setup function the Arkose Labs API uses to configure its use
  // and the callbacks that it triggers
  function setupDetect(detectionObject) {
    window.Arkose = detectionObject;
    window.Arkose.setConfig({
      selector: '#arkose-detect',
      onReady: () => {},
      onShow: () => {},
      onSuppress: () => {},
      onCompleted: (response) => {},
      onError: (response) => {}
      });
  }

  // Make the setup function a global variable so once the Arkose Labs API 
  // is loaded, it can run it. This variable's name MUST match the setup function's 
  // name as defined in the Arkose Labs component 'data-callback' attribute
  window.setupDetect = setupDetect;

Injecting the script

This example React component handles injecting the <script> that loads the Arkose Platform API into an SPA. It also specifies that setupEnforcement or setupDetect from the previous section is the value of the data-callback attribute, so it will be used to configure the API. Please use the appropriate code view for the product you are using , either Arkose Protect or Arkose Detect.

import React from "react";
import { useEffect } from "react";

const ArkoseLabs = (props) => {
  window.Arkose = {};

  function createArkoseScript(props) {
    const script = document.createElement("script");
    script.type = "text/javascript";
    script.src =
      "https://client-api.arkoselabs.com/v2/" + props.publicKey + "/api.js";
    script.setAttribute("data-callback", "setupEnforcement");
    script.async = true;
    script.defer = true;
    script.id = "arkose-script";

    document.head.append(script);

    return () => {
      const object = document.getElementById("arkose-script");
      object.remove();
    };
  }

  // We only want to have the API script tag created once
  useEffect(() => {
    return createArkoseScript(props);
    // eslint-disable-next-line
  }, []);

  return <div id="arkose-ec"></div>;
};

export default ArkoseLabs;
import React from "react";
import { useEffect } from "react";

const ArkoseLabs = (props) => {
  window.Arkose = {};

  function createArkoseScript(props) {
    const script = document.createElement("script");
    script.type = "text/javascript";
    script.src =
      "https://client-api.arkoselabs.com/v2/" + props.publicKey + "/api.js";
    script.setAttribute("data-callback", "setupDetect");
    script.async = true;
    script.defer = true;
    script.id = "arkose-script";

    document.head.append(script);

    return () => {
      const object = document.getElementById("arkose-script");
      object.remove();
    };
  }

  // We only want to have the API script tag created once
  useEffect(() => {
    return createArkoseScript(props);
    // eslint-disable-next-line
  }, []);

  return <div id="arkose-detect"></div>;
};

export default ArkoseLabs;

Using the Component

With the setup function and the component in place, you can load and run the Arkose Platform API:

return (
    <div>
      <ArkoseLabs publicKey={'11111111-1111-1111-1111-111111111111'} />
    </div>
  );

Full Example React Application

A complete example of a React SPA using the Arkose Platform is on the Arkose Github.

Multi-Public Key Implementation

Many SPAs support multiple user flows e.g. login and registration. While such SPAs can use the same public key, if each flow requires different treatment then you must use multiple public keys. For example, if the flows have different rate limits due to seeing many more login requests than registration requests.

Once the Arkose Platform API has been loaded into the DOM with a public key, you cannot fully remove the API and reload it with a different public key. Thus, for multi-public key situations, we recommended implementing the Arkose Platform API within a locally hosted iFrame. Since it has its own DOM, pulling down the iFrame will fully remove the Arkose Platform API.

iFrame Implementation

The following HTML code is an example of the content loaded into the iFrame. It gets the public key used within the Arkose Platform API from the publicKey query parameter passed in when loading the page. This public key is then used when loading the Arkose Platform API in the <script> tag.

The parent.postMessage command posts back to the parent window any callbacks triggered by the Arkose Platform API allowing communication between the iFrame and its parent window.

Please use the appropriate code view for the product you are using , either Arkose Protect or Arkose Detect.

<html>
  <head>
    <meta charset="utf-8" />
    <title>Authentication</title>
    <script>
      // Get the query params used to set up the Arkose API
      const params = new Proxy(new URLSearchParams(window.location.search), {
        get: (searchParams, prop) => searchParams.get(prop),
      });

      // Create a script element to load the Arkose API with the passed in public key
      script = document.createElement('script');
      script.type = 'text/javascript';
      script.async = true;
      script.defer = true;
      script.src =
        '//client-api.arkoselabs.com/v2/' + params.publicKey + '/api.js';
      script.setAttribute('data-callback', 'setupEnforcement');

      document.getElementsByTagName('head')[0].appendChild(script);

      // Setup function called by the Arkose API once it has loaded. 
      // Because this is an iFrame, parent.postMessage sends messages from the 
      // Arkose callbacks to the parent page, letting the parent page then
      // perform the necessary actions
      function setupEnforcement(myEnforcement) {
        myEnforcement.setConfig({
          selector: '#arkose',
          styleTheme: params.theme,
          language: params.mkt,
          mode: 'inline',
          onCompleted: function (response) {
            parent.postMessage(
              JSON.stringify({
                eventId: 'challenge-complete',
                payload: {
                  sessionToken: response.token,
                },
              }),
              '*'
            );
          },
          onReady: function (response) {
            parent.postMessage(
              JSON.stringify({
                eventId: 'challenge-loaded',
              }),
              '*'
            );
          },
          onSuppress: function (response) {
            parent.postMessage(
              JSON.stringify({
                eventId: 'challenge-suppressed',
              }),
              '*'
            );
          },
          onShown: function (response) {
            parent.postMessage(
              JSON.stringify({
                eventId: 'challenge-shown',
              }),
              '*'
            );
          },
        });
      }
    </script>
  </head>

  <body style="margin: 0px">
    <div id="arkose"></div>
  </body>
</html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Authentication</title>
    <script>
      // Get the query params used to set up the Arkose API
      const params = new Proxy(new URLSearchParams(window.location.search), {
        get: (searchParams, prop) => searchParams.get(prop),
      });

      // Create a script element to load the Arkose API with the passed in public key
      script = document.createElement('script');
      script.type = 'text/javascript';
      script.async = true;
      script.defer = true;
      script.src =
        '//client-api.arkoselabs.com/v2/' + params.publicKey + '/api.js';
      script.setAttribute('data-callback', 'setupDetect');

      document.getElementsByTagName('head')[0].appendChild(script);

      // Setup function called by the Arkose API once it has loaded. 
      // Because this is an iFrame, parent.postMessage sends messages from the 
      // Arkose callbacks to the parent page, letting the parent page then
      // perform the necessary actions
      function setupDetect(myDetect) {
        myDetect.setConfig({
          selector: '#arkose',
          styleTheme: params.theme,
          language: params.mkt,
          mode: 'inline',
          onCompleted: function (response) {
            parent.postMessage(
              JSON.stringify({
                eventId: 'detect-complete',
                payload: {
                  sessionToken: response.token,
                },
              }),
              '*'
            );
          },
          onReady: function (response) {
            parent.postMessage(
              JSON.stringify({
                eventId: 'detect-loaded',
              }),
              '*'
            );
          },
          onSuppress: function (response) {
            parent.postMessage(
              JSON.stringify({
                eventId: 'detect-suppressed',
              }),
              '*'
            );
          },
         },
        });
      }
    </script>
  </head>

  <body style="margin: 0px">
    <div id="arkose"></div>
  </body>
</html>

iFrame React Component

Below is a React Component example of loading the HTML content detailed above. This component returns an iframe tag configured to load the necessary HTML content.

It also creates a global event listener that catches messages sent from the iFrame and then carries out the necessary logic within the app. When the components are unmounted, this listener is removed from the parent window to ensure there aren’t multiple listeners in place when using multiple public keys.

Please use the appropriate code view for the product you are using, either Arkose Protect Arkose Detect.

import { useEffect } from 'react';

const ArkoseLabsiFrame = (props) => {
  const addArkoseScript = (props) => {
    // Adds a listener to the main window that can react to messages from 
    // the Arkose iFrame
    function arkoseiFrameListener(event) {
      try {
        var jEvent = JSON.parse(event.data);
        switch (jEvent.eventId) {
          case 'challenge-loaded':
            console.log('onLoaded');
            break;
          case 'challenge-suppressed':
            console.log('onSuppress');
            break;
          case 'challenge-shown':
            console.log('onShown');
            break;
          case 'challenge-complete':
            console.log('onComplete');
            props.onComplete(jEvent.payload.sessionToken);
            break;
          default:
        }
      } catch (error) {}
    }
    window.addEventListener('message', arkoseiFrameListener);

    return () => {
      window.removeEventListener('message', arkoseiFrameListener);
    };
  };

  useEffect(() => {
    return addArkoseScript(props);
    // eslint-disable-next-line
  }, []);

  return (
    <div>
      <iframe
        title='Authentication'
        id='arkose-iframe'
        src={'/arkose-iframe-content.html?publicKey=' + props.publicKey}
        width={302}
        height={290}
        style={{
          borderWidth: 3,
          borderStyle: 'outset',
          borderColor: props.borderColor,
          backgroundColor: '#eee',
        }}
      ></iframe>
    </div>
  );
};

export default ArkoseLabsiFrame;
import { useEffect } from 'react';

const ArkoseLabsiFrame = (props) => {
  const addArkoseScript = (props) => {
    // Adds a listener to the main window that can react to messages from 
    // the Arkose iFrame
    function arkoseiFrameListener(event) {
      try {
        var jEvent = JSON.parse(event.data);
        switch (jEvent.eventId) {
          case 'detect-loaded':
            console.log('onLoaded');
            break;
          case 'detect-suppressed':
            console.log('onSuppress');
            break;
          case 'detect-complete':
            console.log('onComplete');
            props.onComplete(jEvent.payload.sessionToken);
            break;
          default:
        }
      } catch (error) {}
    }
    window.addEventListener('message', arkoseiFrameListener);

    return () => {
      window.removeEventListener('message', arkoseiFrameListener);
    };
  };

  useEffect(() => {
    return addArkoseScript(props);
    // eslint-disable-next-line
  }, []);

  return (
    <div>
      <iframe
        title='Authentication'
        id='arkose-iframe'
        src={'/arkose-iframe-content.html?publicKey=' + props.publicKey}
        width={302}
        height={290}
        style={{
          borderWidth: 3,
          borderStyle: 'outset',
          borderColor: props.borderColor,
          backgroundColor: '#eee',
        }}
      ></iframe>
    </div>
  );
};

export default ArkoseLabsiFrame;

The above component passes in the public key and a reference to the function that should be called when the onComplete callback is triggered. Any Arkose Platform API callback could be implemented in this way letting the application logic be located near where it is needed.

// Function called when the onComplete callback is triggered allowing for the token to then be sent
// to the customers server for verification
const handleOnComplete = (token) => {
    alert(token);
    setRegister(false);
    setLogin(false);
};

return (
    <div className='App'>
      <h1>Arkose Multi-key Example</h1>
      {!login && !register ? (
        <div>
          <p>Select a page</p>
          <br></br>
          <button id='login-button' onClick={handleLoginClick}>
            Login
          </button>
          <button id='register-button' onClick={handleRegisterClick}>
            Register
          </button>
        </div>
      ) : null}
      {login ? (
        <div>
          <h1>Login Page</h1>
          {/* Use the public key you want to protect this particular flow */}
          <ArkoseLabsiFrame
            publicKey='11111111-1111-1111-1111-111111111111'
            onComplete={handleOnComplete}
            borderColor='red'
          />
        </div>
      ) : null}
      {register ? (
        <div>
          <h1>Registration Page</h1>
          {/* Use the public key you want to protect this particular flow */}
          <ArkoseLabsiFrame
            publicKey='11111111-1111-1111-1111-111111111111'
            onComplete={handleOnComplete}
            borderColor='blue'
          />
        </div>
      ) : null}
    </div>
  );