Iframe Setup Guide

This page shows details about how to set up and use the Arkose Bot Manager in the context of an iframe.

📘

Please note that Arkose Bot Manager can host the domain/iframe for use by a customer. Please contact your CSM (Customer Success Manager) or SE for additional information.

📘

iframe-auth to be deprecated.

Previous uses of iframe-auth in URLs have been changed to use iframe.

How to Use the iframe

The code below shows a full example of how to load the hosted Arkose Bot Manager iframe in lightbox mode.

Before implementation, please reach out to your CSM, as you will need to set up any required public keys to use a hosted iframe template with lightbox mode enabled.

Once setup the customer can then use the hosted iframe url that we provide to them in their implementation.

Example Implementation

This example shows an event listener added to a button that will open load the hosted Arkose Bot Manager iframe in lightbox mode when clicked.

<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Hosted Iframe Example</title>
    <style>
      #arkoseFrame {
        width: 0px;
        height: 0px;
        display: none;
        border: none;
      }
      #arkoseFrame.open {
        display: block;
        height: 100%;
        width: 100%;
        position: absolute;
        top: 0;
        left: 0;
      }
    </style>
    <script>
      document.addEventListener('DOMContentLoaded', function(event) { 
        document.getElementById('openButton').addEventListener('click', () => {
            openArkoseIframe();
          })
      });

      // Function used to trigger the Arkose Enforcement Challenge to open
      var openArkoseIframe = function () {
        // Select the required Arkose hosted iframe
        var iframe = document.getElementById('arkoseFrame');
        // The message object to send to the hosted iframe when we want the challenge to open
        var message = {
          publicKey: '11111111-1111-1111-1111-111111111111',
          eventId: 'challenge-open',
        }
        // Sends the message to the hosted iframe
        iframe.contentWindow.postMessage(JSON.stringify(message), '*');
      }

      window.addEventListener('message', function(event) {
        if (!event.data || typeof event.data === 'object') return;
        var json_parsed_event = JSON.parse(event.data);
        var arkoseIframe = document.getElementById('arkoseFrame');

        switch (json_parsed_event.eventId) {
          case 'challenge-loaded':
            document.getElementById('openButton').removeAttribute('disabled');
            break;
          case "challenge-suppressed":
            break;
          case 'challenge-complete':
            console.log(json_parsed_event.payload.sessionToken);
            break;
          case 'challenge-show':
            // Add classname to the iframe to make the iframe lightbox visible
            arkoseIframe.classList.add('open');
            break;
          case "challenge-shown":
            break;
          case 'challenge-hide':
            // Remove classname from the iframe to hide the iframe again
            arkoseIframe.classList.remove('open');
            break;
        }
      });
    </script>
  </head>
  <body>
    <iframe id="arkoseFrame" src="https://iframe.arkoselabs.com/11111111-1111-1111-1111-111111111111/lightbox.html"></iframe>
    <button id="openButton" disabled>Open Lightbox</button>
  </body>
</html>

<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Identity Page</title>
    <style>
        iframe {
            width: 0px;
            height: 0px;
            border: 0 !important;
            overflow-y: hidden;
        }
    </style>
    <script>
        window.addEventListener("message", function (event) {
            try {
              var json_parsed_event = JSON.parse(event.data)
  
              switch (json_parsed_event.eventId) {
                  case "challenge-loaded":
                      document.getElementById("arkoseFrame").style.height = json_parsed_event.payload.frameHeight;
                      document.getElementById("arkoseFrame").style.width = json_parsed_event.payload.frameWidth;
                      break;
                  case "challenge-suppressed":
                      break;
                  case "challenge-complete":
                      alert(json_parsed_event.payload.sessionToken)
                      break;
              }
            } catch (e) {
              // Ignore errors on JSON parsing message events that aren't specific to Arkose Labs
            }
        });
    </script>
</head>

<body>
    <iframe id="arkoseFrame" src="https://iframe.arkoselabs.com/11111111-1111-1111-1111-111111111111/index.html">
    </iframe>
</body>

</html>

CSS Required

For lightbox mode to work, you need to either add CSS or dynamically update style properties on the hosted iframe DOM element.

The default CSS for the iframe should hide the iframe completely, in our example above we are setting both the height and width to 0 and display to none to ensure the iframe is not visible unless lightbox mode is activated.

With this we have a CSS class setup that is appended to the iframe when we want to trigger lightbox mode. This class sets the height and width of the iframe to 100%, makes the iframe visible, and positions the iframe on top of the current page. These styles are required to make lightbox mode work correctly.


Inline Mode

🚧

Inline mode for our Enforcement Challenge is not a recommended integration approach as it is not a standard integration but there are use cases that may be applicable to you.

The code below shows a full example of how to load the hosted Arkose Bot Manager iframe.

<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Identity Page</title>
    <style>
        iframe {
            width: 0px;
            height: 0px;
            border: 0 !important;
            overflow-y: hidden;
        }
    </style>
    <script>
        window.addEventListener("message", function (event) {
            try {
              var json_parsed_event = JSON.parse(event.data)
  
              switch (json_parsed_event.eventId) {
                  case "challenge-loaded":
                      document.getElementById("arkoseFrame").style.height = json_parsed_event.payload.frameHeight;
                      document.getElementById("arkoseFrame").style.width = json_parsed_event.payload.frameWidth;
                      break;
                  case "challenge-suppressed":
                      break;
                  case "challenge-complete":
                      alert(json_parsed_event.payload.sessionToken)
                      break;
                  case "challenge-shown":
                      console.log("Challenge Shown");
                      console.log(json_parsed_event.payload);
                      break;
                  case "challenge-iframeSize":
                      document.getElementById("arkoseFrame").style.height = json_parsed_event.payload.frameHeight;
                      document.getElementById("arkoseFrame").style.width = json_parsed_event.payload.frameWidth;
                      break;
              }
            } catch (error) {
              // Ignore errors on JSON parsing message events that aren't specific to Arkose Labs
            }
        });
    </script>
</head>

<body>
    <iframe id="arkoseFrame" src="https://iframe.arkoselabs.com/11111111-1111-1111-1111-111111111111/index.html">
    </iframe>
</body>

</html>

Iframe Events

The iframe events are listed below. Please note the appropriate name and description depends on whether you are using our enforcement or detection components.

EventTypeDescription (Enforcement)Description (Detection)
challenge-loadedeventChallenge has finished loading.Detection has finished loading.
challenge-suppressedeventChallenge will not be presented to the user (Good User Case).Detection is running and analyzing the user intent.
challenge-completeeventChallenge solved / user is validated as good.Detection is complete.
challenge-showneventChallenge is presented to the user.Not applicable.
challenge-iframesizeeventHeight and Width of the content within the iframe (For Dynamic Styling).Not applicable.
challenge-failedeventWhen a challenge is failed more than a configured number of tries. Defaults to no limit value for the trigger.Not applicable.
challenge-erroreventChallenge has encountered an error.Detection or Challenge has encountered an error.
challenge-warningeventChallenge has encountered a warning.Detection or Challenge has encountered a warning.
challenge-showeventBefore Challenge is presented to the user, only applicable in lightbox mode.Not applicable.
challenge-hideeventAfter Challenge has been closed or hidden, only applicable in lightbox mode.After Detection is completed.

URL Reference

This is a breakdown of the URL which needs to be loaded in the iframe:

  • Domain: https://iframe.arkoselabs.com
  • Path Options:
    • public key: In the examples above this is 11111111-1111-1111-1111-111111111111. Replace this with the public key Arkose Labs gives you.
  • URL Params:
    • mkt: This is the parameter for passing in a language code. For a list of our supported language codes see Supported Languages.

📘

Please note that Arkose Labs can host the iframe for merchants.

Hosting your Own iframe

Although Arkose Labs generally hosts customer iframes, customers may choose to host their own iframe.

Creating the Iframe Page and Domain

When using this solution:

  • A separate domain must be set up.
  • A page with the Arkose Bot Manager code must be hosted from it.

Creating the Domain

Create and host the domain using your standard process. An example domain name is <https://iframe.arkoselabs.com>.

Creating the Hosted Page

The following is example code that could be used to load the Arkose Bot Manager iframe. Please see our Client-Side Instructions for more information on the Arkose Bot Manager implementation. Please select the code view for either our enforcement and detection components, depending on which one you are using.

📘

Please note that some accessibility tools (e.g. Windows narrator) will use the words in the title section as spoken text for this iframe. Update the title to comply with your branding strategy.

<html>

<head>
    <meta charset="utf-8">
    <title>Authentication</title>
    <script>

        function getAllUrlParams(url) {
            // get query string from url (optional) or window
            var queryString = url ? url.split('?')[1] : window.location.search.slice(1);

            // we'll store the parameters here
            var obj = {};

            // if query string exists
            if (queryString) {

                // stuff after # is not part of query string, so get rid of it
                queryString = queryString.split('#')[0];

                // split our query string into its component parts
                var arr = queryString.split('&');

                for (var i = 0; i < arr.length; i++) {
                    // separate the keys and the values
                    var a = arr[i].split('=');

                    // set parameter name and value (use 'true' if empty)
                    var paramName = a[0];
                    var paramValue = typeof (a[1]) === 'undefined' ? true : a[1];

                    // (optional) keep case consistent
                    paramName = paramName.toLowerCase();
                    if (typeof paramValue === 'string') paramValue = paramValue.toLowerCase();

                    // if the paramName ends with square brackets, e.g. colors[] or colors[2]
                    if (paramName.match(/\[(\d+)?\]$/)) {

                        // create key if it doesn't exist
                        var key = paramName.replace(/\[(\d+)?\]/, '');
                        if (!obj[key]) obj[key] = [];

                        // if it's an indexed array e.g. colors[2]
                        if (paramName.match(/\[\d+\]$/)) {
                            // get the index value and add the entry at the appropriate position
                            var index = /\[(\d+)\]/.exec(paramName)[1];
                            obj[key][index] = paramValue;
                        } else {
                            // otherwise add the value to the end of the array
                            obj[key].push(paramValue);
                        }
                    } else {
                        // we're dealing with a string
                        if (!obj[paramName]) {
                            // if it doesn't exist, create property
                            obj[paramName] = paramValue;
                        } else if (obj[paramName] && typeof obj[paramName] === 'string') {
                            // if property does exist and it's a string, convert it to an array
                            obj[paramName] = [obj[paramName]];
                            obj[paramName].push(paramValue);
                        } else {
                            // otherwise add the property
                            obj[paramName].push(paramValue);
                        }
                    }
                }
            }
            return obj;
        }

        // Setup Arkose Script
        var pathArray = window.location.pathname.split('/')

        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.async = true;
        script.defer = true;
        script.src = '//client-api.arkoselabs.com/v2/' + pathArray[1] + '/api.js'
        script.setAttribute('data-callback', 'setupEnforcement');

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

        // Let this function run on a set interval. It will review the Arkose Content that loads and send the page sizing for the iframe to the parent
        var interval = setInterval(function () {
            frameHeight = document.getElementById("fc-iframe-wrap").offsetHeight;
            frameWidth = document.getElementById("fc-iframe-wrap").offsetWidth;
            parent.postMessage(JSON.stringify({
                eventId: "challenge-iframeSize",
                payload: {
                    frameHeight: frameHeight,
                    frameWidth: frameWidth
                }
            }), "*")
        }, 3000);

        function setupEnforcement(myEnforcement) {
            var params = getAllUrlParams(window.location.href);

            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",
                    }), "*");
                },
                onFailed: function (response) {
                    parent.postMessage(JSON.stringify({
                        eventId: "challenge-failed",
                        payload: {
                            sessionToken: response.token
                        }
                    }), "*");
                },
                onError: function (response) {
                    parent.postMessage(JSON.stringify({
                        eventId: "challenge-error",
                        payload: {
                            error: response.error
                        }
                    }), "*");
                },
                onResize: function (response) {
                  var defaultHeight = 450;
                  var defaultWidth = 400;
                  var height = response && response.height ? response.height : defaultHeight;
                  var width = response && response.width ? response.width : defaultWidth;
                  try {
                    if (typeof height === 'string') {
                      height = height.replace('px', '');
                      height = parseInt(height, 10);
                      if (isNaN(height)) {
                        height = defaultHeight;
                      }
                    }
                    if (typeof width === 'string') {
                      width = width.replace('px', '');
                      width = parseInt(width, 10);
                      if (isNaN(width)) {
                        width = defaultWidth;
                      }
                    }
                  } catch (e) {
                    height = defaultHeight;
                    width = defaultWidth;
                  }
                    parent.postMessage(JSON.stringify({
                      eventId: "challenge-iframeSize",
                      payload: {
                        frameHeight: height,
                        frameWidth: width
                      }
                    }), "*")
                }
            });
        }

    </script>
</head>

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

</html>

<html>

<head>
    <meta charset="utf-8">
    <title>Authentication</title>
    <script>

        function getAllUrlParams(url) {
            // get query string from url (optional) or window
            var queryString = url ? url.split('?')[1] : window.location.search.slice(1);

            // we'll store the parameters here
            var obj = {};

            // if query string exists
            if (queryString) {

                // stuff after # is not part of query string, so get rid of it
                queryString = queryString.split('#')[0];

                // split our query string into its component parts
                var arr = queryString.split('&');

                for (var i = 0; i < arr.length; i++) {
                    // separate the keys and the values
                    var a = arr[i].split('=');

                    // set parameter name and value (use 'true' if empty)
                    var paramName = a[0];
                    var paramValue = typeof (a[1]) === 'undefined' ? true : a[1];

                    // (optional) keep case consistent
                    paramName = paramName.toLowerCase();
                    if (typeof paramValue === 'string') paramValue = paramValue.toLowerCase();

                    // if the paramName ends with square brackets, e.g. colors[] or colors[2]
                    if (paramName.match(/\[(\d+)?\]$/)) {

                        // create key if it doesn't exist
                        var key = paramName.replace(/\[(\d+)?\]/, '');
                        if (!obj[key]) obj[key] = [];

                        // if it's an indexed array e.g. colors[2]
                        if (paramName.match(/\[\d+\]$/)) {
                            // get the index value and add the entry at the appropriate position
                            var index = /\[(\d+)\]/.exec(paramName)[1];
                            obj[key][index] = paramValue;
                        } else {
                            // otherwise add the value to the end of the array
                            obj[key].push(paramValue);
                        }
                    } else {
                        // we're dealing with a string
                        if (!obj[paramName]) {
                            // if it doesn't exist, create property
                            obj[paramName] = paramValue;
                        } else if (obj[paramName] && typeof obj[paramName] === 'string') {
                            // if property does exist and it's a string, convert it to an array
                            obj[paramName] = [obj[paramName]];
                            obj[paramName].push(paramValue);
                        } else {
                            // otherwise add the property
                            obj[paramName].push(paramValue);
                        }
                    }
                }
            }
            return obj;
        }

        // Setup Arkose Script
        var pathArray = window.location.pathname.split('/')

        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.async = true;
        script.defer = true;
        script.src = '//client-api.arkoselabs.com/v2/' + pathArray[1] + '/api.js'
        script.setAttribute('data-callback', 'setupDetect');

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

        function setupDetect(myDetect) {
            var params = getAllUrlParams(window.location.href);

            myDetect.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",
                    }), "*")
                },
                onError: function (response) {
                    parent.postMessage(JSON.stringify({
                        eventId: "challenge-error",
                        payload: {
                            error: response.error
                        }
                    }), "*");
                }
            });
        }

    </script>
</head>

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

</html>

Please note the following regarding the above sample code:

getAllUrlParams - A reference function for parsing url parameters if you need to pass any important information through the iframe. An example of important information is language code. See the URL reference section later in this page for more information about URL parameters.

parent.postMessage - Pass event data back to the parent domain. For example, passing back the session token to submit to the server-side.

challenge-iframeSize - Not applicable for detection component. This runs on an interval for dynamic resizing of the iframe on the parent element. This data can also be parsed and passed through as part of the Arkose Bot Manager loaded event.

Important Styling

There are a couple styles that will need to be added in order to make the challenge fully accessible and responsive to zooms/smaller screens when the implementation shows the challenge in a Modal fashion. The following code snippet displays the required styles/setup.

<div class="arkose-modal-wrapper" style="display: flex; flex-direction: column;">
  <div class="arkose-iframe-holder" style="overflow: auto;">
    <iframe title="Arkose Challenge" src="{{hosted iframe url}}">
  </div>
</div>

The parent wrapper, arkose-modal-wrapper, will need to have display: flex and flex-direction: column applied to allow the child div to grow within. To arkose-iframe-holder, overflow: auto is needed to allow for scrolling.

Accessibility

Due to the nature of iframes, the out of the box accessibility of Arkose’s Challenge can be impacted. This is because iframes cannot hi-jack the focus of the parent page. It is recommended that focus is manually set on the Challenge Loaded event. Example code:

<script>
  window.addEventListener('message', function(event) {
    var eventData = JSON.parse(event.data);
    
    if (eventData.eventId === 'challenge-loaded') {
      var iframe = document.getElementById('arkoseFrame');
      if(iframe) iframe.focus();
    }
  })
</script>
<body>
  <iframe src="https://iframe.arkoselabs.com/<PUBLIC_KEY>/index.html" id="arkoseFrame"></iframe>
</body>