Subresource Integrity (SRI)

Overview

SRI (Subresource Integrity) lets a browser verify that content delivered from a third party is valid and has not been altered. You provide a cryptographic hash that must match a fetched resource. Arkose Labs provides customers with a service that delivers the integrity hashes necessary to safely load our resources.

In more detail, you specify a base64-encoded cryptographic hash of a file fetched by the browser. This hash is the value of any <script> or <link> element’s integrity attribute. This value has at least one string, and each string is a prefix specifying a hash algorithm (allowed prefixes are sha256, sha384, and sha512), followed by a dash, and ending with the base64-encoded hash.

For more details about SRI in general, see its Mozilla Developers documentation page.

This guide explains how to enable SRI for your Arkose Labs Platform implementation. It applies to both Arkose Detect and Arkose Protect.

Client-Side Setup

SRI requires an additional function that gathers needed implementation information. The following code samples, one for non-Internet Explorer browsers and one for Internet Explorer, include the queryParamsAPI() function. It uses your public key to retrieve the SRI hash needed to verify that you can safely load the needed Arkose Script.

loadArkoseScript() generates the Arkose Script with your public key. It also sets the integrity header to the SRI response value. The sample code has two options for how to proceed if there was a failure getting the SRI value or if the SRI value is incorrect. Choose the option appropriate for your application and remove the code for the other.

  • Fail Close: If the SRI value is not found or is incorrect, the Arkose Script is not loaded, blocking user access.

  • Fail Open: If the SRI value is not found or the API had a networking issue, the Arkose Script is loaded without doing the integrity check. However, if the hashes don't match, the script is not loaded by the browser. Instead, onerror() is called.

To use one of the code samples, do the following, which is also specified in their comments. These steps apply to both code samples/all browsers.

  1. In <head></head>, dynamically inject the Arkose Labs Script so that it will run after the SRI hashes are retrieved.

  2. Include a trigger element anywhere in your page. It can be added to the DOM at any time.

  3. In the queryParamsAPI() and loadArkoseScript() function definitions, replace <YOUR PUBLIC KEY> with the public key supplied to you by Arkose Labs.

  4. In loadArkoseScript(), choose which option to do in case of problems with the fetched SRI value, either Fail Close or Fail Open, and delete the code for the non-used option.

  5. If you use the Fail Close option:

    • Add code in arkoseScript.onerror to handle the downloaded script’s value not matching the provided hash. How you handle this error situation is up to you.

    • After that code, in the location designated by a comment, add code to handle there being no downloaded value and thus the Enforcement Challenge not running (its script will not be injected into the document head). How you handle this error situation is up to you.

  6. If you use the Fail Open option:

    • Add code in arkoseScript.onerror to handle the downloaded script’s value not matching the provided hash. How you handle this error situation is up to you.

    • Ensure the name of the final defined function, in the sample setupEnforcement, is the same name previously defined in loadArkoseScript as the value of the data-callback attribute. You can change this name, but it must be the same in both places.

Sample code for non-Internet Explorer browsers

Please see the next section for an Internet Explorer specific code sample.

<html>
<head>
  <!-- Arkose Labs Script tag should be dynamically injected after SRI hashes are retrieved -->
  <!-- inject Arkose Script here -->
  <link rel="shortcut icon" href="#">
  <meta charset="UTF-8">
</head>
<body>
<!--
  The trigger element can exist anywhere in your page and can be added to the DOM at any time.
-->
<button id="enforcement-trigger">
  trigger element
</button>
<script>
  /*
    Arkose Labs Script tag should be dynamically injected into the <head></head> after SRI hashes are retrieved.
    Remember to replace the <YOUR PUBLIC KEY> with the public key supplied to you by Arkose Labs.
  */
  function queryParamsAPI() {
    /*
      Query Arkose Params API with your public key to retrive SRI hash to load the Arkose Script.
    */
    fetch("https://client-api.arkoselabs.com/params/sri/< YOUR PUBLIC KEY >")
      .then(response => response.json())
      .then(data => {
        const sri = data.hashes.js['api.js'];
        loadArkoseScript(sri);
      })
      .catch(error => {
        loadArkoseScript(null);
      });
  }
   
  queryParamsAPI()

  /*
    This will generate the Arkose Script with your public key and set the integrity header to the value of SRI response.
    Customers may choose how to handle failures by either blocking user access or loading
    the script without an integrity check.
  */
  function loadArkoseScript(sri) {
    let arkoseScript = document.createElement('script');

    arkoseScript.setAttribute('src', '//client-api.arkoselabs.com/v2/< YOUR PUBLIC KEY >/api.js');
    arkoseScript.setAttribute('data-callback', 'setupEnforcement');    
    arkoseScript.defer = true;
    arkoseScript.async = true;

    /*
      Option One: Fail Close - This will not load the script if SRI was not found.
    */
    if (sri) {
      arkoseScript.setAttribute('crossorigin', 'anonymous');
      arkoseScript.setAttribute('integrity', sri);
      
      arkoseScript.onerror = function() {
        // Script was downloaded but it did not match the hash provided.
        // Handle integrity check failure.
      }
      document.head.appendChild(arkoseScript);
    }
    
    // If SRI is not found, won't run EC challenge (script won't be injected to document head) 
    // Handle challenge not loading here
    /*
      -------------------------------------------------------------------------------
    */
    
    
    /*
      Option Two: Fail Open - This will proceed without the integrity check if SRI was not found.
    */
    if (sri) {
      arkoseScript.setAttribute('crossorigin', 'anonymous');
      arkoseScript.setAttribute('integrity', sri);
    }

    arkoseScript.onerror = function() {
      // Script was downloaded but it did not match the hash provided.
      // Handle integrity check failure.
    }
    document.head.appendChild(arkoseScript);
    /*
      ---------------------------------------------------------------------------------
    */
  }

  /*
    This global function will be invoked when the API is ready and SRI hashes have been validated. Ensure the name is the same name
    that is defined on the attribute `data-callback` attribute above which loads the api for your
    public key.
  */
  function setupEnforcement(myEnforcement) {
    myEnforcement.setConfig({
      selector: '#enforcement-trigger',
      onCompleted: function(response) {
        console.log(response.token);
      }
    });
  }
</script>
</body>
</html>

Sample code for Internet Explorer setup

<html>
<head>
  <!-- Arkose Labs Script tag should be dynamically injected after SRI hashes are retrieved -->
  <!-- inject Arkose Script here -->
  <link rel="shortcut icon" href="#">
  <script
      type="text/javascript"
      src="https://unpkg.com/[email protected]/dist/jquery.min.js"
  ></script>
  <meta charset="UTF-8">
</head>
<body>
<!--
  The trigger element can exist anywhere in your page and can be added to the DOM at any time.
-->
<button id="enforcement-trigger">
  trigger element
</button>
<script>
  /*
    Arkose Labs Script tag should be dynamically injected into the <head></head> after SRI hashes are retrieved.
    Remember to replace the <YOUR PUBLIC KEY> with the public key supplied to you by Arkose Labs.
  */
  function queryParamsAPI() {
    /*
      Query Arkose Params API with your public key to retrive SRI hash to load the Arkose Script. 
      Please note this example is tailored to be compatible with Internet Explorer.
    */
   var url = 'https://client-api.arkoselabs.com/params/sri/< YOUR PUBLIC KEY >';

    $.ajax({
      url: url,
      type: 'GET',
      dataType: 'json',
      timeout: 5000,
      success: function(data) {
        if (data) {
          var sri = data.hashes.js['api.js'];
          console.log('Hash: ', sri);
          loadArkoseScript(sri);
        }
      },
      error: function(_, status, e) {
        console.log('Error: ', e);
      }
    });
  }
   
  queryParamsAPI()

  /*
    This will generate the Arkose Script with your public key and set the integrity header to the value of SRI response.
    Customers may choose how to handle failures by either blocking user access or loading
    the script without an integrity check.
  */
  function loadArkoseScript(sri) {
    let arkoseScript = document.createElement('script');

    arkoseScript.setAttribute('src', '//client-api.arkoselabs.com/v2/< YOUR PUBLIC KEY >/api.js');
    arkoseScript.setAttribute('data-callback', 'setupEnforcement');    
    arkoseScript.defer = true;
    arkoseScript.async = true;

    /*
      Option One: Fail Close - This will not load the script if SRI was not found.
    */
    if (sri) {
      arkoseScript.setAttribute('crossorigin', 'anonymous');
      arkoseScript.setAttribute('integrity', sri);
      
      arkoseScript.onerror = function() {
        // Script was downloaded but it did not match the hash provided.
        // Handle integrity check failure.
      }
      document.head.appendChild(arkoseScript);
    }
    
    // If SRI is not found, won't run EC challenge (script won't be injected to document head) 
    // Handle challenge not loading here
    /*
      -------------------------------------------------------------------------------
    */
    
    
    /*
      Option Two: Fail Open - This will proceed without the integrity check if SRI was not found.
    */
    if (sri) {
      arkoseScript.setAttribute('crossorigin', 'anonymous');
      arkoseScript.setAttribute('integrity', sri);
    }

    arkoseScript.onerror = function() {
      // Script was downloaded but it did not match the hash provided.
      // Handle integrity check failure.
    }
    document.head.appendChild(arkoseScript);
    /*
      ---------------------------------------------------------------------------------
    */
  }

  /*
    This global function will be invoked when the API is ready and SRI hashes have been validated. Ensure the name is the same name
    that is defined on the attribute `data-callback` attribute above which loads the api for your
    public key.
  */
  function setupEnforcement(myEnforcement) {
    myEnforcement.setConfig({
      selector: '#enforcement-trigger',
      onCompleted: function(response) {
        console.log(response.token);
      }
    });
  }
</script>
</body>
</html>