Arkose on Akamai - Reference Architecture

Overview

Akamai Technologies is a content delivery network that operates proxy servers around the world to accelerate web traffic and provide other value-added services. Akamai can also integrate with 3rd party services such as Arkose Labs. This document describes the workflow and implementation with Akamai.

The logic to verify the Arkose Labs token when handling a call to a protected endpoint would normally be handled by the web servers of Arkose Labs’ customers. When customers use Akamai to accelerate their traffic, the logic can be offloaded to the CDN. This not only helps streamline the integration of new customers or new endpoints but also shifts malicious traffic to the CDN layer, saving the customer’s web server bandwidth for legitimate traffic. When the Arkose Labs Fraud and Abuse Prevention Platform (Arkose Labs Platform) is integrated with Akamai, the CDN layer will take care of the following steps:

  • If the arkosesessiontoken header is missing from the request to the protected endpoint, block the request and respond to the client “Invalid or Missing Arkose Session Token

  • Otherwise:

    • Forward the request to Arkose Labs’ verify API

    • Process the response from the Arkose Labs API and decide whether to block the request (solved=false) or forward it to the origin web server (solved=true)

Workflow

Exchange workflow without Akamai

  1. The client loads a page (for example login form) with an endpoint protected with the Arkose Labs Platform (the login request). The page returned by the origin web server includes tags for the Arkose Labs JavaScript API

  2. The client parses the content of the HTML page and loads the page assets including the Arkose Labs JavaScript. The relevant section of the page, for example, the login button, is instrumented to invoke the Arkose client API to initiate the detection and enforcement process

  3. The JavaScript runs on the client side. It collects various client characteristics, or a fingerprint, and sends it to the Arkose Labs server. The Arkose Labs server creates a session, evaluates the fingerprint, and if a threat is found a signal will be sent back to the client indicating that a challenge must be shown, otherwise the challenge will be suppressed. The challenge type and difficulty depend on the threat and pressure applied to the detection method or telltale.

  4. During the challenge process, several messages are sent to the Arkose Labs server while the user interacts with the challenge. The user interactions are defined as events, such as game loaded or user clicked verify

  5. The user triggers a request to the protected endpoint, by clicking Verify (user clicked verify event ). The Arkose Labs token is sent along with the request. The customer origin web server extracts the token and makes an API call to the Arkose Labs server to get a classification (human or bot). If the Arkose Labs API returns solved=false, the client-side code rejects/denies the request. The same action is taken when the token is missing from the request. Otherwise, if solved=true is returned, it proceeds with the request.

In this model, the Arkose Labs customer must make two changes to integrate with the Arkose Labs Platform:

  1. Add the necessary code to instrument the call to the protecting endpoint to invoke the Arkose client API

  2. Update their back-end logic to make an API call to Arkose labs to verify the token on the protected requests

Exchange workflow between the client, Akamai, the origin web server and the Arkose verify API:

 

Exchange workflow with Akamai

The Akamai CDN layer offers plenty of flexibility and gives us the opportunity to offload some of the logic to the Akamai layer, which will help streamline the integration: In particular, we’ll be able to offload the logic to call the verify API on step 5 above to validate the Arkose session token to the Akamai CDN layer. The Akamai edge server will then be able to block sessions that fail to verify from reaching the origin web server, which offer an additional DDoS protection

Steps 1 to 4 of the exchange workflow with the CDN layer are the same as previously described. The main change is step 5 with regards to handling failed and successful verification. The workflow diagrams below show the altered behavior in the case of a failed and successful verifications.

Failed to verify

  1. Following the detect and enforce process, the ASrkose Labs server sends a token to the client. this will trigger a callback to the protected endpoint.

  2. The Arkose Labs token is sent along with the request. The Akamai proxy server extracts the token and makes an API call to the Arkose Labs server to get the request classification (human or bot). If the Arkose Labs API returns solved=false, or if the token is missing, the CDN layer will reject/deny the request

Successful Verify

  1. The user triggers a request to the protected endpoint by clicking the Verify button

  2. The Arkose Labs token is sent along with the request. The Akamai proxy server extracts the token and makes an API call to the Arkose Labs server to get the request classification (human or bot). If the Arkose Labs API returns solved=true, the CDN layer will allow the request to proceed to the customer origin web server.

 

Integration steps

This integration consists of two parts:

  1. Arkose Labs client-side JavaScript additions

  2. The server-side Arkose Labs token verification check is configured using the Akamai Native XML language.

Client-Side Setup

In order to use the Arkose Labs Platform, some JavaScript additions need to be made to the page you want to protect, such as your Login or Registration page. Documentation for the client-side additions can be found in the Arkose Labs standard integration guides:

Standard Setup

These guides walk through the JavaScript additions. When grabbing the Arkose Labs session token as part of the submission to the server-side, the token must be sent as a request header called arkosesessiontoken. Here’s what the header and its value would look like:

arkosesessiontoken: 35460485f134e4a89.4456731002|r=us-west-2|metabgclr=#ffffff|guitextcolor=#000000|metaiconclr=#757575|meta=3|pk=3707C1C9-9840-4A83-9015-6CD5C29F7BE1|at=40|ag=101|cdn_url=https://cdn.arkoselabs.com/fc|lurl=https://audio-us-west-2.arkoselabs.com|surl=https://client-api.arkoselabs.com

On requests to the protected endpoints, the Akamai edge servers will extract the arkosesessiontoken and make a call to the Arkose verify API. When the header is missing, the Akamai edge server will generate an error.

The following code is an example of a basic login page that invokes the Arkose Labs detection and enforcement process on the submit button and passes the arkosesessiontoken header as a header:

<html>
<head>
  <script src="//client-api.arkoselabs.com/v2/<YOUR_PUBLIC_KEY>/api.js" data-callback="setupEnforcement" async defer></script>
</head>
<body>

<!--
  The trigger element can exist anywhere in your page and can be added to the DOM at any time.
-->
  <h1>Test web site with Fastly integration</h1>
  <br/>
  <form method="post" id='loginForm' target="_self" action="./login.php">
    <input id=username type="text">
    <input id=password type="text">
    <input id="arkoseID" name="arkoseID" type="hidden" value="">
    <input type="submit" id="submit-id" onclick="return false;">
  </form>

<!--
  To configure the enforcement place a script tag just before the closing <body> tag and define the
  callback as a global function.
-->
<script>
 
  /*
    This global function will be invoked when the API is ready. Ensure the name is the same name
    that is defined on the attribute `data-callback` in the script tag that loads the api for your
    public key.
  */
  function setupEnforcement(myEnforcement) {
    myEnforcement.setConfig({
      selector: '#submit-id',
      onCompleted: function(response) {
        console.log(response.token);
        var xhttp = new XMLHttpRequest();
        var jsondata = JSON.stringify({
            username: document.getElementById("username").value,
            password: document.getElementById("password").value
          });
        xhttp.open("POST", './login.php');
        xhttp.setRequestHeader('arkosesessiontoken', response.token);
        xhttp.send(jsondata);
		document.getElementById("loginForm").reset();
      }
    });
  }

</script>
</body>
</html>

It may not always be possible to send the session token as a header. Alternatively, the session token can be sent as a query string (GET and POST requests) or included in the body (POST request). The edge logic is set up to primarily look for the session in the arkosesessiontoken header but will fall back to look for the token in the arkosesessiontoken parameter in the URL query string and arkosesessiontoken parameter in the body if the header is not present in the request.

Server-Side Setup (Akamai)

Setup a delivery configuration

The content delivery configuration will support the communication between the Akamai edge servers and the Arkose verify API. Arkose Labs recommends using the following settings:

Configuration setting

Value

Digital property name

arkose.$customer.com

Caching rule

no-store

origin

verify-api.arkoselabs.com

Origin settigns

  • Host header value: use origin hostname

  • Connection timeout: 500ms

  • Read timeout: 2s

  • Enforce HTTPS, port 443

Advanced metadata for the token verification process

This section describes the property manager variables and advanced metadata to be added in the delivery configuration supporting the protected endpoint.

Property manager variable:

Variable

Description and values

PMUSER_ARKOSE_VERIFY_ENABLED

Kill switch.

To enable the token verification process, set the value to true. Otherwise set the value to false.

Recommended default: true

PMUSER_ARKOSE_EDGE_DENY

When this variable is set to true or not defined, this will cause the edge server to generate a 403 response (deny) when the token is either invalid, missing or the API generated an error during he validation process. When set to false, the edge server will not take any blocking action at the edge and pass the information to the origin server.

Recommended default: true

PMUSER_ARKOSE_VERIFY_HOSTNAME

The digital property that supports the communication between the Akamai edge server and the Arkose Labs verify API (arkose.$customer.com)

PMUSER_ARKOSE_VERIFY_ORIGIN_SIGNAL_ENABLED

This feature when set to true will send the result of the Arkose Verify evaluation downstream to the origin server.

Recommended default: true

Advanced metadata:

The advanced metadata has some environment variable that controls the behavior and must be updated to match the protected endpoints.

Setting

Description and value

url_path_1

The path of the URL for the protected endpoints, for example:

<match:uri.wilcard value=”/login”>

You may further refine the scope by the addition of conditions such as:

  • Match on hotname: <match:hoit host=”www.customer.com”>

  • Match on method: <match:request.method value="GET">

  • Match on query string name / value pairs: <match:uri.query-string name=”qs1” value=”value1”>

The Akamai professional services representative can help refine the conditions and add necessary metadata tags.

ARKOSE_EP_BODY_FORMAT

For POST request only, supported values are JSON or URLENCODED

ARKOSE_PRIVATE_SECRET

Enter the private key provided by Arkose Labs.

Akamai advanced metadata

<!-- ARKOSE LABS ON AKAMAI ## TEMPLATE BEGIN ##
    This template is designed to facilitate the integration of Arkose Labs on the Akamai CDN when customer are taking 
    advantage of the Arkose detect and enforce product. This template handle the following steps:
    1. For  requests to the protected endpoint, extract Arkose token from teh request body or query string parameter
    2. validate the token calling the Arkose Labs verify API
    3. Deny requests if the Arkose labs Token is invalid
    4. Send the result of the evalaution in a custom header if the Arkose labs token is valid (optional)
-->
<!-- ### ENVIRONMENT VARIABLES ### -->
<!-- Protected endpoints list, repepat this snippet for each protected endpoint and ensure the correct key is asscoaied with the correct endpoint -->
<match:uri.wildcard value="url_path_1">
    <!-- POST body format: JSON or URLENCODED -->
    <assign:variable>
        <name>ARKOSE_EP_BODY_FORMAT</name>
        <value>JSON</value>
        <hidden>on</hidden>
    </assign:variable>
    <!-- Private key -->
    <assign:variable>
        <name>ARKOSE_PRIVATE_SECRET</name>
        <value>update_private_key</value>
        <hidden>on</hidden>
    </assign:variable>
</match:uri.wildcard>

<!-- ### DO NOT MODIFIED BEYOND THIS POINT ### -->
<match:variable name="PMUSER_ARKOSE_VERIFY_ENABLED" value="true">
    <!-- hostname to access Arkose verify API -->
    <assign:variable>
        <name>ARKOSE_VERIFY_API_HOST</name>
        <value>%(PMUSER_ARKOSE_VERIFY_HOSTNAME)</value>
        <hidden>on</hidden>
    </assign:variable>
    <!-- Controls whether the edge server denies the request if the token is missing,invalid or an error occured -->
    <assign:variable>
        <name>ARKOSE_EDGE_DENY</name>
        <value>%(PMUSER_ARKOSE_EDGE_DENY)</value>
        <hidden>on</hidden>
    </assign:variable>
    <!-- Force origin signaling if no blocking action is enforced at the edge -->
    <match:variable name="ARKOSE_EDGE_DENY" value="false">
        <assign:variable>
            <name>PMUSER_ARKOSE_VERIFY_ORIGIN_SIGNAL_ENABLED</name>
            <value>false</value>
            <hidden>on</hidden>
        </assign:variable> 
    </match:variable>
    <!-- Origin signaling -->
    <assign:variable>
        <name>ARKOSE_VERIFY_ORIGIN_SIGNAL_ENABLED</name>
        <value>%(PMUSER_ARKOSE_VERIFY_ORIGIN_SIGNAL_ENABLED)</value>
        <hidden>on</hidden>
    </assign:variable>

    <!-- ### Extract the token from the request to the protected endpoint ### -->
    <!-- Attempt to extract the token from the arkosesessiontoken header -->
    <match:request.header name="arkosesessiontoken">
        <assign:extract-value>
            <variable-name>ARKOSE_TOKEN_VALUE</variable-name>
            <location>Client_Request_Header</location>
            <location-id>arkosesessiontoken</location-id>
            <hidden>on<hidden>
        </assign:extract-value>
    <!-- If the header is missign attemtp to extract the token from the query string parermeter or the POST body -->
    <match:request.header name="arkosesessiontoken" result="false"> 
        <match:uri.query-string name="arkosesessiontoken">
            <!-- extract the token from a query string parameter -->
            <assign:extract-value>
                <variable-name>ARKOSE_TOKEN_VALUE</variable-name>
                <location>Query_string</location>
                <location-id>arkosesessiontoken</location-id>
                <hidden>on</hidden>
            </assign:extract-value>
        </match:uri.query-string>
        <match:variable name="ARKOSE_TOKEN_VALUE" value="*?" value-wildcard="on" result="false">
            <match:request.method value="POST PUT">
                <!-- Enable body inspection -->
                <cache:post-caching.status>on</cache:post-caching.status>
                <cache:post-caching.max-size>32KB</cache:post-caching.max-size>
                <cache:bypass>on</cache:bypass>
                <edgeservices:inspect-request-body>
                    <status>on</status>
                    <status-multipart-inspection>on</status-multipart-inspection>
                    <error-response-on-limit>off</error-response-on-limit>
                    <free-body-early>off</free-body-early>
                    <allow-put>on</allow-put>
                    <allow-patch>on</allow-patch>
                </edgeservices:inspect-request-body>
                <match:compare select="REQUEST_BODY_RAW" op="contains" arg2="arkosesessiontoken">
                    <match:variable name="ARKOSE_EP_BODY_FORMAT" value"URLENCODED">
                        <match:regex impl="re2" regex=".*" select="ARGS_POST:arkosesessiontoken">
                            <assign:variable>
                                <name>ARKOSE_TOKEN_VALUE</name>
                                <value>%(AK_SELECT)</value>
                                <transform>
                                    <url-decode/>
                                </transform>
                                <hidden>on</hidden>
                            </assign:variale>
                        </match:regex>
                    </match:variable>
                    <match:variable name="ARKOSE_EP_BODY_FORMAT" value"JSON">
                        <match:regex impl="re2" regex=".*" select="JSON:'/arkosesessiontoken'">
                            <assign:variable>
                                <name>ARKOSE_TOKEN_VALUE</name>
                                <value>%(AK_SELECT)</value>
                                <transform>
                                    <url-decode/>
                                </transform>
                                <hidden>on</hidden>
                            </assign:variale>
                        </match:regex>
                    </match:variable>
                </match:compare>
            </match:request.method>
        </match:uri.wildcard>
    </match:request.header>
    <!-- ### Verify the token by calling the Arkose Lbas API ### -->
    <match:metadata-stage value="client-request-body">
        <!-- If the token was successfully extracted from the request, make an API call to teh Arkose Labs verify API -->
        <match:variable name="ARKOSE_TOKEN_VALUE" value="*?" value-wildcard="on">
            <edgeservices:processing-agent>
                <status>on</status>
                <url>/api/v2/verify/?maprule=%(AK_MAPRULE)</url>
                <host>%(ARKOSE_VERIFY_API_HOST)</host>
                <log-r-line>on</log-r-line>
                <!-- no cookies -->
                <cookies>
                    <agent-to-client/>
                    <agent-to-origin/>
                    <client-to-agent/>
                </cookies>
                <post-body>private_key=%(ARKOSE_PRIVATE_SECRET)&amp;session_token=%(ARKOSE_TOKEN_VALUE)</post-body>
            </edgeservices:processing-agent>
        </match:variable>
        <match:variable name="ARKOSE_TOKEN_VALUE" value="*?" value-wildcard="on" result="false">
            <assign:variable>
                <name>ARKOSE_TOKEN_EVAL</name>
                <value>token_missing</value>
                <hidden>on</hidden>
            </assign:variable>
            <match:variable name="ARKOSE_EDGE_DENY" value="false" result="false">
                <auth:acl.deny>%(ARKOSE_TOKEN_EVAL)</auth:acl.deny>
            </match:variable>
        <match:variable>
    </match:metadata-stage>
    <!-- ## Evaluate the verify API response ## -->
    <match:processing-agent-response host="%(ARKOSE_VERIFY_API_HOST)">
        <!-- Default failure code -->
        <assign:variable>
            <name>ARKOSE_TOKEN_EVAL</name>
            <value>other_failures</value>
            <hidden>on</hidden>
        </assign:variable>
        <!-- Redirect errors -->
        <match:response.status value="301 302 307"> 
            <assign:variable>
                <name>ARKOSE_TOKEN_EVAL</name>
                <value>service_redirect</value>
                <hidden>on</hidden>
            </assign:variable>
        </match:response.status>
        <!-- Authnetication errors -->
        <match:response.status value="401 403"> 
            <assign:variable>
                <name>ARKOSE_TOKEN_EVAL</name>
                <value>server_access_denied</value>
                <hidden>on</hidden>
            </assign:variable>
        </match:response.status>
        <!-- Service unavailable -->
        <match:response.status value-in-range="500:504"> 
            <assign:variable>
                <name>ARKOSE_TOKEN_EVAL</name>
                <value>service_unavailable</value>
                <hidden>on</hidden>
            </assign:variable>
        </match:response.status>
        <!-- successful transaction, extract the response -->
        <match:response.status value="200">
            <assign:extract-value>
                <variable-name>ARKOSE_VERIFY_RESPONSE_HEADER</variable-name>
                <location>IPA_Response_Header</location>
                <location-id>Arkose-Verify</location-id>
                <hidden>on</hidden>
            </assign:extract-value>
            <assign:variable>
                <name>ARKOSE_TOKEN_EVAL</name>
                <value>%(ARKOSE_VERIFY_RESPONSE_HEADER)</value>
                <transform>
                    <extract-param>
                        <name>solved</name>
                        <separator>;</separator>
                    </extract-param>
                </transform>
                <hidden>on</hidden>
            </assign:variable>
            <assign:variable>
                <name>ARKOSE_TOKEN_ERROR</name>
                <value>%(ARKOSE_VERIFY_RESPONSE_HEADER)</value>
                <transform>
                    <extract-param>
                        <name>error</name>
                        <separator>;</separator>
                    </extract-param>
                </transform>
                <hidden>on</hidden>
            </assign:variable>
        </match:response.status>
        <!-- If the token was invalid, block the request and serve a 403. 
        Akamai can setup a custom response, out of the default Arkose Labs integration scope -->
        <match:variable name="ARKOSE_TOKEN_EVAL" value="false">
            <match:variable name="ARKOSE_TOKEN_ERROR" value="?*" value-widcard="on" result="false">    
                <assign:variable>
                    <name>ARKOSE_TOKEN_EVAL</name>
                    <value>token_invalid</value>
                    <hidden>on</hidden>
                </assign:variable>
            </match:variable>
            <match:variable name="ARKOSE_TOKEN_ERROR" value="?*" value-widcard="on">    
                <assign:variable>
                    <name>ARKOSE_TOKEN_EVAL</name>
                    <value>api_error</value>
                    <hidden>on</hidden>
                </assign:variable>
            </match:variable>
            <match:variable name="ARKOSE_EDGE_DENY" value="false" result="false">
                <auth:acl.deny>%(ARKOSE_TOKEN_EVAL)</auth:acl.deny>
            </match:variable>    
        </match:variable>
        <match:variable name="ARKOSE_TOKEN_EVAL" value="true">
            <assign:variable>
                <name>ARKOSE_TOKEN_EVAL</name>
                <value>token_valid</value>
                <hidden>on</hidden>
            </assign:variable>
        </match:variable>
    </match:processing-agent-response>
    <!-- Forward verify header value to origin in case of success -->
    <match:metadata-stage value="forward-request">
        <match:variable name="ARKOSE_VERIFY_ORIGIN_SIGNAL_ENABLED" value="true">
            <match:variable name="ARKOSE_TOKEN_EVAL" value="true">
                <edgeservices:modify-outgoing-request.add-header>
                    <status>on</status>
                    <name>Arkose-Eval</name>
                    <value>%(ARKOSE_TOKEN_EVAL)</value>
                </edgeservices:modify-outgoing-request.add-header>
            </match:variable>
        </match:variable>
    </match:metadata-stage>
</match:variable>
<!-- ARKOSE LABS ON AKAMAI ## TEMPLATE END ## -->

Error code and events

Event type

Description

Blocking event

The following will trigger an error. A synthetic response will be served to the client. The content of the response can be customized.

  • token_missing: the arkosesessiontoken header was missing from the request

  • token_invalid : the token is not valid

  • api_error : the verify API returned an error.

Non blocking events

The other condition relates to detect issues that can be caused by transient communication issues or the verify API being unstable. For those cases, the request should be passed to the origin web server (fail open). The result of the evaluation process will be passed to the origin in the “Arkose-Result” header:

  • token_valid: The token was successfully validated

  • service_unavailable: The request to the verify API caused a 5xx response code

  • service_access_denied: The request to the verify API caused a 4xx response code

  • service_redirect: The request to the verify API caused a 3xx response code

  • other_failure: an unknown error occurred

For further assistance setting up the Arkose Labs verification process on Akamai, please contact your Arkose Labs or Akamai services representative.

Was this article helpful?
1 out of 1 found this helpful