HUMAN Decisioning Worker

The HUMAN Decisioning Worker is a library to quickly set up a BotGuard integration on pre-existing web applications using Cloudflare Workers.

Overview

HUMAN Decisioning Worker can be used to set up BotGuard's Active Mitigation or a Token Mitigation on sensitive endpoints. This worker handles injecting the required client-side logic to run BotGuard's Detection Tag and Block or Forward API calls to your server. Additionally, this solution is fully extendable to forward BotGuard's Decision about your user traffic upstream, allowing you to attach your own custom handlers.

Notes

  • Active Mitigation:
    • Provides mitigation on your web applications in real time by fetching a Decision from BotGuard's Mitigation API.
    • Best suited for defending on individual interactive events (e.g. account creation, authentication, commenting or liking, checking out, etc.) and mission critical API access.
  • Token Mitigation:
    • Provides broad mitigation by fetching a Decision and storing an encrypted token on the client that can be used to provide constant decisioning and protection.
    • Best suited as overall application protection such as scraping protection.

Actions

The HUMAN Worker provides bot mitigation through these core actions:

  • deployTag:
    • Injects the HUMAN client-side logic on the web page to be protected.
    • This action should be set on routes that return HTML back.
  • createToken:
    • Creates and extends expirations on Decisions nested in the HUMAN token as an encrypted cookie string.
    • This token represents the decision from the Mitigation API that can be used to pre-check a human/bot before allowing access to any sensitive endpoints.
    • There should be only be one route set for this action per domain.
  • checkToken
    • Verifies the decision in the HUMAN token from the createToken action above.
    • If bot, it returns a configurable block response to the client. Otherwise, it forwards the request to the upstream server for the request.
  • checkLive
    • Verifying a user directly with the Mitigation API
    • If bot, it returns a configurable block response to the client. Otherwise, it forwards the request to the upstream server for the request.
  • formData
    • Verifying a user directly on events using the native form submit action
    • If bot, it returns a configurable block response to the client. Otherwise, it forwards the request to the upstream server for the request.

Features

  • Handles turnkey mitigation from HUMAN.
  • Extendable to add custom middleware.
  • TypeScript Support.

Installation

A package of the library is available, please contact your HUMAN representative for access to the latest distribution. There are additional example use cases available as recipes. Please reach out if you need additional guidance on your integration.

Basic Example

You will need the human.js file from your HUMAN representative along with a Cloudflare account with a domain configured.

// In your index.js file
// PASTE the contents of the `human.js`file here (this exposes the library via the human var)

const config = {
  /* Your configuration object */
};

// handleRequest is the default version of the HUMAN Worker all inclusive of routing and handling all actions
const { handleRequest } = human.initialize(config);

addEventListener("fetch", (event) => {
  event.passThroughOnException();
  event.respondWith(handleRequest(event.request));
});

Set up the Environment

You must add your Mitigation API key to your worker's Environment variables. In your Cloudflare dashboard, please go to the settings for your HUMAN Worker service. You will see the section for adding Environment variables as depicted below:

Cloudflare Worker Settings

Please enter the environment variable name MITIGATION_API_KEY and enter your API key where you read YOUR-VESPER-API-KEY and encrypt this value. You should see the value is set as below:

Mitigation API Set

Configuration Object

Config

human.initialize(config)

Name Type(s) Description Required Default
allowEmptyDecision string If there are no decisions or HUMAN token, allows protected endpoints. true
blockJSON object Response JSON to offer to the client in case a block decision is detected. {Response: 'DENIED'}
blockStatusCode number Response HTTP Status Code to offer to the client in case a block decision is detected. 403
DEBUG boolean Boolean used to enable console log records for Worker code.  
customerId string Your HUMAN provided Customer ID. false
decisionOnHumanResponseError string If the call to Vesper errors, the default decision for createToken. "allow"
enableInterceptor boolean Enables the monkey patches for waiting for HUMAN token Ajax calls on client. true
failOpen boolean Boolean for failing open or closed (true - fail open / false - fail closed).  
humanDecisionTimeout number Max timeout for calls to the HUMAN decisioning API (measured in ms). 300
humanSecurityUrl string URL for the HUMAN decisioning API. "https://vesper.p.botx.us/decision"
humanTokenBodyJSONResponseFail object The response JSON to offer to the client if the cookie was not set correctly. {hT: 'fail'}
humanTokenBodyJSONResponseRefresh object The response JSON to offer to the client if the cookie needs to be refreshed by the client. {hT: 'refresh'}
humanTokenBodyJSONResponseSuccess object The response JSON to offer to the client if the cookie was set correctly. {hT: 'success'}
humanTokenCookieDomainScope string The scope for which the created httponly cookie should be delivered.  
humanTokenCookieName string The name of the cookie to set on the response that is embeded in the client runtime. "hT"
humanTokenSecret string String used for Symmetric Decision Encryption - MUST Match the string in HUMAN Check Worker.  
humanTokenTTL number The time to trust a decision being passed by a client runtime (measured in ms). 30000
maxFailCount number Maximum number of times to fail getting a token before stopping the pulse. 10
maxHeaderSize number The maximum header size on which to chunk the signal across headers. 4000
nativeForm boolean If True, the worker will only deploy the mo=2 tag without any of the client stub. Only use on pages that have native form submission. This config does NOT support token or live check features. false
passiveOnly boolean If true, the worker will only deploy the mo=0 tag without any of the client stub needed to run rest of the mitigation features. Only use deployTag with this enabled. false
policyName string Name of any policy set for the Mitigation API.  
routes array of Route Routes to declare various action handlers, see Route table below.  
showInResponseBody boolean If enabled the decision will be shown in both the JSON and HTTPOnly cookie. false
signalRefresh number The interval on which to refresh the HUMAN decision token. 10000
siteId string A site identifier used to specify the site on which HUMAN is implemented.  
tagDomain string The domain of the HUMAN tag.  
tagId string Your HUMAN provided Tag Identifier.  
tagPath string The path of the HUMAN tag.  
tagOptions object Any additional options you may want to add to the Detection tag's query params.  
timeout number How long to wait before timing out when trying to retrieve a HUMAN token. 25000
tokenJSONKeyName string The token JSON key for the HUMAN token. "hT"
tokenName string Name of the header containing the HUMAN token. "human-token"
tokenURL string URL path for HUMAN's token generator. "ht"

Route

Name Type(s) Description Required?
type string One of the actions: 'deployTag', 'createToken', 'checkToken', 'checkLive', 'formData'
includedPathRegex string A string representing the regex to test against the pathname for the route to activate
methods array of HTTP methods 'GET', 'PUT', 'POST', 'DELETE'
eventType string The eventType value of the event that is being protected on checkLive & formData routes

Advanced Usage

If you are using wrangler and/or miniflare which is out of scope for this example, you will need a worker directory configured to deploy your code. Once you have this, you can use the library by importing it as shown below. Otherwise, you must copy the contents as the preview example.

1. Initialize HUMAN Worker

import human from "./human.js";

const config = {
  /* Your configuration object*/
};

const humanWorker = human.initialize(config);

2. Setup Handler

const {
  findMatchingRoute,
  deployTag,
  checkToken,
  createToken,
  checkLive,
  getForwardRequest,
} = humanWorker;

// This is the same implementation as the handleRequest exported in the default humanWorker
const handleRequest = async (request) => {
  // Finds the matching route in the config
  const matchingRoute = findMatchingRoute(request);

  switch (matchingRoute?.type) {
    case "deployTag":
      return deployTag(request);
    case "checkToken":
      return checkToken(request);
    case "createToken":
      return createToken(request);
    case "checkLive":
      return checkLive(request, { eventType: matchingRoute.eventType });
  }

  // All routes that do not match should also use `getForwardRequest` to remove
  // any human cookies or update domains on forward requests
  return fetch(getForwardRequest(request));
};

addEventListener("fetch", (event) => {
  event.passThroughOnException();
  event.respondWith(handleRequest(event.request));
});

3. Setup Handler for a single action

If you want the worker to only handle a single action without routing, you can use the individual actions as shown here:

// Create Token Worker
const { createToken } = humanWorker;

addEventListener("fetch", (event) => {
  event.respondWith(createToken(event.request));
});

// Deploy Tag Worker
const { deployTag } = humanWorker;

addEventListener("fetch", (event) => {
  event.respondWith(deployTag(event.request));
});

// Check Token Worker
const { checkToken } = humanWorker;

addEventListener("fetch", (event) => {
  event.respondWith(checkToken(event.request));
});

// Check Live Worker
const { checkLive } = humanWorker;

addEventListener("fetch", (event) => {
  event.respondWith(checkLive(event.request, { eventType: "EVENT_TYPE" }));
});

Additional Functions

In addition to the base actions that come with the HUMAN Worker, the following provided functions allow more customization:

  • injectHumanScript:
    • Used in deployTag handler.
    • Takes a Response object with a Content-Type of html, and appends the HUMAN client logic to that response.
    • This action should be set on routes that return HTML back.
  • getCheckLiveResult:
    • Used in checkLive handler.
    • Creates and extends expirations on Decisions nested in the HUMAN token as an encrypted cookie string.
    • This token represents the decision from the Mitigation API that can be used to pre-check a human/bot before allowing access to any sensitive endpoints.
    • There should be only be one route set for this action per domain.
  • getCheckTokenResult
    • Used in checkToken handler.
    • Verifies the decision in the HUMAN token from the createToken action above.
    • If bot, it returns a configurable block response to the client. Otherwise, it forwards the request to the upstream server for the request.
  • getCreateTokenResult
    • Used in createToken handler.
    • Verifies a user directly with the Mitigation API.
    • If bot, it returns a configurable block response to the client. Otherwise, it forwards the request to the upstream server for the request.
  • askHuman
    • Directly make a request to the Mitigation API.
  • getMitigationParams
    • Retrieves the parameters for the Mitigation API from the request headers, returns '[EMPTY]' for each param if not found.
  • getForwardRequest
    • Clones the request and updates the URL to the originDomain in config if there are any, and removes any HUMAN cookies & headers in the upstream request.
  • findMatchingRoute
    • Returns a matching route for the request if any exists.
  • removeUpstreamHUMANHeaders
    • Removes any HUMAN headers or cookies from the request.

These examples show how the base actions can be used to create your own handlers:

const {
  injectHumanScript,
  getCheckLiveResult,
  getCheckTokenResult,
  getCreateTokenResult,
} = humanWorker;

// If the response is html, then attach the HUMAN client logic
async function deployTag(request) {
  const serverResponse = await fetch(request)
  const contentType: string | null = serverResponse.headers.get('Content-Type')

  if (contentType?.startsWith('text/html')) {
    return injectHumanScript(serverResponse)
  }

  return serverResponse
}

// If the Active decision is allow, then forward the request
async function checkLive(request, { eventType }){
  const checkLiveResult = await getCheckLiveResult(request, , { eventType })
  if (checkLiveResult.result === 'allow') {
    return fetch(request)
  }

  return new Response("Forbidden", { status: 403 });
}

// If the Token decision is allow, then forward the request
async function checkToken(request) {
  const checkTokenResult = await getCheckTokenResult(request, , { eventType })
  if (checkTokenResult.result === 'allow') {
    return fetch(request)
  }

  return new Response("Forbidden", { status: 403 });
}

Middleware

Creating custom middleware with the HUMAN worker is very straightforward by wrapping the additional functions. The HUMAN Worker works well with the itty-router library.

Example 1: Middleware for attaching a HUMAN Decision on Token routes
import { Router } from "itty-router";
import human from "./human.js";

const config = {
  /* Your configuration object*/
};

const humanWorker = human.initialize(config);
const { getCheckTokenResult } = humanWorker;

// itty-router
const router = Router();

// Appends the human decision on the request
const withHumanTokenDecision = async (request) => {
  const checkTokenResult = await getCheckTokenResult(request);
  request.humanDecision = checkTokenResult;
};
Example 2: Middleware for Mitigating account creation routes
import { Router } from "itty-router";
import human from "./human.js";

const config = {
  /* Your configuration object*/
};

const humanWorker = human.initialize(config);
const { getCheckLiveResult } = humanWorker;

// For a live route for account creation
const withHumanActiveDecision = async (request) => {
  const checkLiveResult = await getCheckLiveResult(request, { eventType: "2" });
  request.humanDecision = checkLiveResult.decision;
};

// Middleware that only passes allow decision
const requireAllowAction = (request) => {
  if (!request.humanDecision?.action === "allow") {
    return new Response("Forbidden", { status: 403 });
  }
};

const createAccount = (request) => {
  return new Response("Account creation success", { status: 200 });
};

router.get(
  "/signup",
  withHumanActiveDecision,
  requireAllowAction,
  createAccount
);

TypeScript

The library is built with, and fully supports, TypeScript. In the folder of the library's distribution, you will see the definitions for the library in the file human.d.ts.