Skip to content

Arcjet fingerprints

Arcjet tracks clients across multiple requests so that suspicious activity can be detected. This is done by fingerprinting each request within your environment through a component within the Arcjet SDK.

The fingerprint is a SHA-256 string generated based on request characteristics. The goal is that a single client will always generate the same fingerprint so we can detect suspicious requests and block the client. Behind the scenes, Arcjet uses this to cache decisions locally in your environment and in the Arcjet decision API.

By default, the fingerprint is generated based on the client IP address. This can be configured by specifying different characteristics. Different configurations can be used for each supported rule and globally on the SDK root instance.

Built-in options are managed by the SDK and are always available. If multiple options are provided, they will be combined.

OptionDescription
ip.srcClient IP address
http.hostRequest host
http.request.headers["<header_name>"]Request header value
http.request.cookie["<cookie_name>"]Request cookie value
http.request.uri.args["<query_param_name>"]Request query value
http.request.uri.pathRequest path

The client IP address is a key part of the fingerprint. The IP should not be trusted as it can be spoofed in most cases, especially when loaded via the Headers object. Each Arcjet SDK uses a native method to detect the IP address generally following the guidance provided by the MDN X-Forwarded-For documentation.

When choosing the IP address to use, Arcjet uses the first non-private IP that is a valid address. However, in non-production environments, we allow private/internal addresses so the SDKs work correctly. See the individual SDK references for more details.

You can use custom characteristics to track clients based on various attributes. For example, you can set up a limit for a logged in user by setting their user ID as the identifier. This will apply the limit to all requests made by that user regardless of their device and IP address.

Custom characteristics are defined with a string key when configuring the sdk. The value is then passed as a string, number or boolean when calling the protect method. You can use any string value for the key.

In this example we create a custom characteristic called userId, but you can call it anything you like:

import arcjet, { fixedWindow } from "@arcjet/next";
const aj = arcjet({
key: process.env.ARCJET_KEY!,
rules: [
fixedWindow({
characteristics: ["userId"], // track requests by a custom user ID
mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
window: "60s", // 60 second fixed window
max: 100, // allow a maximum of 100 requests
}),
],
});

Then when calling the protect method, you would pass a value for the key:

// Pass userId as a string to identify the user. This can be any string,
// number or boolean value. Multiple values will be combined.
const decision = await aj.protect(req, { userId: "user123" });

If you want to be more flexible and use the user IP address for anonymous users and a user ID for logged in users, you can can use withRule to create augmented clients that use different characteristics.

import arcjet, { fixedWindow } from "@arcjet/next";
const aj = arcjet({
key: process.env.ARCJET_KEY!,
rules: [],
});
const ajForUsers = aj.withRule(
fixedWindow({
characteristics: ["userId"], // track user requests by id
mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
window: "60s", // 60 second fixed window
max: 100, // allow a maximum of 100 requests
}),
);
const ajForGuests = aj.withRule(
fixedWindow({
characteristics: ["ip.src"], // track guest requests by IP address
mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
window: "60s", // 60 second fixed window
max: 100, // allow a maximum of 100 requests
}),
);

Then when calling the protect method, you would pass a value for the key:

if (userIsLoggedIn) {
// Identify logged in users by their user ID
const decision = await ajForUsers.protect(req, { userId: "user123" });
} else {
// Identify guest users by their IP address
// Note that you don't need to manually pass the IP address, the Arcjet SDK
// will automatically retrieve it for you.
const decision = await ajForGuests.protect(req);
}

You can set the characteristics globally on the SDK root instance and per rule. If you set global characteristics, they will apply to all rules unless overridden by a specific rule.

In this example, the bot detection rule will track requests by IP address and the fixed window rule will track requests by a custom user ID. This means that results will be tracked and cached independently.

import arcjet, { fixedWindow, detectBot } from "@arcjet/next";
const aj = arcjet({
key: process.env.ARCJET_KEY!,
characteristics: ["ip.src"],
rules: [
detectBot({
// will inherit the global characteristics (ip.src)
mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
allow: [],
}),
fixedWindow({
characteristics: ["userId"], // track requests by a custom user ID
mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
window: "60s", // 60 second fixed window
max: 100, // allow a maximum of 100 requests
}),
],
});

Discussion