Skip to content

Arcjet / NextAuth integration

Arcjet can protect your NextAuth login and signup routes from brute force attacks and other abuse. You can also use the Auth.js authenticated user ID to implement user-specific rate limits.

What is Arcjet? Arcjet helps developers protect their apps in just a few lines of code. Bot detection. Rate limiting. Email validation. Attack protection. Data redaction. A developer-first approach to security.

Example use case

  • Protect your NextAuth route handlers by rate limiting requests and blocking bots.
  • Limit access to a free API endpoint based on the client IP address.
  • Provide a higher rate limit for authenticated clients based on their NextAuth user ID.

See an example Next.js implementation on GitHub.

Protect NextAuth route handlers

Arcjet can extend the NextAuth route handler to protect it from abuse.

This example configures a rate limit on the NextAuth route POST handler which is used for authentication and logout attempts. It allows up to 10 requests within a 60 second window and also prevents bots from making requests.

NextAuth recommends creating a catch-all API route to handle all authentication requests. Arcjet extends this handler:

/app/api/auth/[...nextauth]/route.ts
// This example is for NextAuth 4, the current stable version
import arcjet, { detectBot, slidingWindow } from "@arcjet/next";
import NextAuth from "next-auth";
import GithubProvider from "next-auth/providers/github";
import { NextResponse } from "next/server";
export const authOptions = {
// Configure one or more authentication providers
// See https://next-auth.js.org/configuration/initialization#route-handlers-app
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
}),
],
};
const handler = NextAuth(authOptions);
const aj = arcjet({
key: process.env.ARCJET_KEY!,
rules: [
slidingWindow({
mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
interval: 60, // tracks requests across a 60 second sliding window
max: 10, // allow a maximum of 10 requests
}),
detectBot({
mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
allow: [], // "allow none" will block all detected bots
}),
],
});
const ajProtectedPOST = async (req: Request, res: Response) => {
// Protect with Arcjet
const decision = await aj.protect(req);
console.log("Arcjet decision", decision);
if (decision.isDenied()) {
if (decision.reason.isRateLimit()) {
return NextResponse.json({ error: "Too Many Requests" }, { status: 429 });
} else {
return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
}
}
// Then call the original handler
return handler(req, res);
};
export { handler as GET, ajProtectedPOST as POST };

Rate limits using NextAuth user ID

Arcjet rate limits allow custom characteristics to identify the client and apply the limit. Using NextAuth’s getServerSession() helpers you can pass through a user ID.

/app/api/private/route.ts
// This example is for NextAuth 4, the current stable version
import arcjet, { tokenBucket } from "@arcjet/next";
import { getServerSession } from "next-auth";
import GithubProvider from "next-auth/providers/github";
import { NextResponse } from "next/server";
export const authOptions = {
// Configure one or more authentication providers
// See https://next-auth.js.org/configuration/initialization#route-handlers-app
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
}),
],
};
// The arcjet instance is created outside of the handler
const aj = arcjet({
key: process.env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com
characteristics: ["user"], // Track based on the Clerk userId
rules: [
// Create a token bucket rate limit. Other algorithms are supported.
tokenBucket({
mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
refillRate: 5, // refill 5 tokens per interval
interval: 10, // refill every 10 seconds
capacity: 10, // bucket maximum capacity of 10 tokens
}),
],
});
export async function GET(req: Request) {
// Get the current user from NextAuth
const session = await getServerSession(authOptions);
if (!session || !session.user || !session.user.email) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// A very simple hash to avoid sending PII to Arcjet. You may wish to add a
// unique salt prefix to protect against reverse lookups.
const email = session.user.email;
const emailHash = require("crypto")
.createHash("sha256")
.update(email)
.digest("hex");
// Deduct 5 tokens from the user's bucket
const decision = await aj.protect(req, {
user: emailHash,
requested: 5,
});
console.log("Arcjet decision", decision);
if (decision.isDenied()) {
return NextResponse.json(
{
error: "Too Many Requests",
reason: decision.reason,
},
{
status: 429,
},
);
}
return NextResponse.json({ message: "Hello World" });
}

Discussion