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.
- Verifies the decision in the HUMAN token from the
- 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.
- Verifying a user directly on events using the native form
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:
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:
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
ofhtml
, and appends the HUMAN client logic to that response. - This action should be set on routes that return HTML back.
- Used in
- 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.
- Used in
- 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.
- Used in
- 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.
- Used in
- 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.
- Clones the request and updates the URL to the
- 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
.