Skip to content

Arcjet IP detection reference

The Arcjet IP detection library provides a utility to find the public IP of a Request.

What are Arcjet utilities?

Arcjet utilities are independent libraries that do not require the use of the main Arcjet SDK - they can be used with or without other Arcjet rules.

We take the pain out of implementing security tasks through these utilities to provide a security as code approach to developer-first security.

The public IP of a Request is difficult to discern, but some platforms provide specific mechanisms for accessing it - such as the X-Real-IP header added or overwritten by Vercel. The @arcjet/ip library provides a streamlined API over these mechanisms based on the current platform.

Terminal window
npm install -S @arcjet/ip
import ip from "@arcjet/ip";
// Some Request-like object, such as node's `http.IncomingMessage`, `Request` or
// Next.js' `NextRequest`
const request = new Request("/your-route");
// Returns the first non-private IP address detected
const publicIp = ip(request);
console.log(publicIp);

Additional guards can be applied with the platform option, such as { platform: "fly-io" }, { platform: "cloudflare" }, or { platform: "vercel" }.

import ip from "@arcjet/ip";
// Some Request-like object, such as node's `http.IncomingMessage`, `Request` or
// Next.js' `NextRequest`
const request = new Request("/your-route");
// Also optionally takes a platform for additional protection
const platformGuardedPublicIp = ip(request, { platform: "fly-io" });
console.log(platformGuardedPublicIp);

Most proxies will add themselves in the chain of public IP addresses. Trusted proxies may be specified with the proxies option, and they will be ignored when detecting a public IP.

import ip from "@arcjet/ip";
// Some Request-like object, such as node's `http.IncomingMessage`, `Request` or
// Next.js' `NextRequest`
const request = new Request("/your-route");
// You can also pass a list of trusted proxies to ignore
const proxyExcludedPublicIp = ip(request, {
proxies: [
"100.100.100.100", // A single IP
"100.100.100.0/24", // A CIDR for the range
],
});
console.log(proxyExcludedPublicIp);

The proxies option also accepts proxy services alongside plain IP addresses. A proxy service knows the IP ranges that belong to a particular provider and the header that provider uses to pass through the real client IP. When a request arrives from one of the service’s IP ranges, the real client IP is read from that header instead of the connecting address.

This is safer than trusting a header on its own: the header is only used when the connecting address falls within the service’s verified IP ranges, so a direct client cannot spoof it.

The cloudflare() helper is the proxy service for Cloudflare. When a request comes from a Cloudflare IP range, ip() reads the real client IP from the CF-Connecting-IP (or CF-Connecting-IPv6) header that Cloudflare sets.

import ip, { cloudflare } from "@arcjet/ip";
// Some Request-like object, such as node's `http.IncomingMessage`, `Request` or
// Next.js' `NextRequest`
const request = new Request("/your-route");
// Pass the `cloudflare()` proxy service to read the real client IP from
// Cloudflare's `CF-Connecting-IP` header, but only when the request comes from
// a known Cloudflare IP range
const publicIp = ip(request, {
proxies: [cloudflare()],
});
console.log(publicIp);

The Cloudflare IP ranges are bundled with @arcjet/ip and kept up to date as the package is released. If Cloudflare publishes a new range before the SDK has been updated, you can override the bundled ranges with the ranges option. This replaces the bundled list rather than adding to it, so supply the full set of ranges from cloudflare.com/ips plus any new range:

import ip, { cloudflare } from "@arcjet/ip";
const request = new Request("/your-route");
// Override the bundled Cloudflare ranges with your own list. This *replaces*
// the bundled ranges, so include the full list from https://www.cloudflare.com/ips/
// plus any new range Cloudflare has added that the SDK doesn't know about yet.
const publicIp = ip(request, {
proxies: [
cloudflare({
ranges: [
// The current Cloudflare ranges...
"173.245.48.0/20",
"103.21.244.0/22",
// ...plus a new range not yet bundled in the SDK
"203.0.113.0/24",
],
}),
],
});
console.log(publicIp);

ip(request: RequestLike, options?: Options)

Section titled “ip(request: RequestLike, options?: Options)”

Look up an IP address in a Request-like object, such as Request, Node’s http.IncomingMessage, or Next.js’ NextRequest.

Types:

type RequestLike = {
info?: PartialInfo | null | undefined;
ip?: unknown;
requestContext?: PartialRequestContext | null | undefined;
socket?: PartialSocket | null | undefined;
headers: Headers | Record<string, string[] | string | undefined>;
};
type Options = {
platform?: Platform | null | undefined;
proxies?: readonly (string | Cidr | ProxyService)[] | null | undefined;
};
type Platform = "cloudflare" | "firebase" | "fly-io" | "render" | "vercel";

Returns a ProxyService for Cloudflare that can be passed in the proxies option. Pass ranges to override the bundled Cloudflare IP ranges (this replaces the bundled list).

Types:

type ProxyService = {
kind: "service";
name: string;
ranges: ReadonlyArray<string | Cidr>;
clientIp: ReadonlyArray<ClientIpHeader>;
};
type CloudflareOptions = {
ranges?: readonly string[] | null | undefined;
};

Arcjet can protect your entire app or individual routes with just a few lines of code. Using the main Arcjet SDK you can setup bot protection, rate limiting for your API, minimize fraudulent registrations with the signup form protection and more.

Discussion