Open davidmytton opened 1 day ago
Assuming you trust the proxies, this workaround will use the first IP in the X-Forwarded-For
header:
export default async function middleware(request: NextRequest) {
// Construct a request object to send to Arcjet with the required fields
// until https://github.com/arcjet/arcjet-js/issues/2346 is resolved. This
// is a temporary workaround to get the IP from the load balancer
const xForwardedFor = request.headers.get("X-Forwarded-For") ?? "127.0.0.1";
const forwardedIps = [];
// As per MDN X-Forwarded-For Headers documentation at
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For
// The `x-forwarded-for` header may return one or more IP addresses as
// "client IP, proxy 1 IP, proxy 2 IP", so we want to split by the comma and
// trim each item.
for (const item of xForwardedFor.split(",")) {
forwardedIps.push(item.trim());
}
// We trust the proxies in the `x-forwarded-for` header, so we'll use the first
// IP address in the list.
const ip = forwardedIps[0];
const arcjetRequest = {
ip,
method: request.method,
host: request.nextUrl.host,
url: request.url,
headers: request.headers,
};
const decision = await arcjet.protect(arcjetRequest);
if (decision.isDenied()) {
return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
} else {
return NextResponse.next();
}
}
// We trust the proxies in the
x-forwarded-for
header, so we'll use the first // IP address in the list.
You should make an allowlist of trusted IPs because pretty much every proxy adds to any header specified. Using the first item in the list allows anyone to bypass all IP-based protections.
If you put your site behind a LB or proxy then they usually add headers in the form of
X-Forwarded-For: <client>, <proxy1>, <proxy2>
Our current implementation is based on running on platforms like Vercel and Fly.io where we can detect the platform and trust the proxy providing us with the real client IP. That's not true on general platforms like AWS or GCP, so we can't trust the
X-Forwarded-For
header. Our implementation processes in reverse and picks the first public IP.https://github.com/arcjet/arcjet-js/blob/007f51caa108792888db27ff8b0c5233e79a08cb/ip/index.ts#L743-L744
We should support the option to provide a list of one or more trusted proxies that we can ignore so our parser gets to the actual client IP.
In the example above we'd allow configuration of
proxy1
andproxy2
so we'd useclient
as the actual IP.See real user report of this in https://github.com/arcjet/arcjet/issues/3606