Next.js
How to verify AI agent tokens and authenticate requests in Next.js App Router using @agentonboard/sdk/next.
The @agentonboard/sdk/next subpath exports requireAgentAccount, built specifically for Next.js App Router route handlers.
It reads the Bearer token from the incoming request, verifies the RS256 signature locally against public JWKS, and resolves your own user record by verified email in a single function call.
1. Install the SDK
npm install @agentonboard/sdk2. Guard your route handler
requireAgentAccount(request, options) accepts the standard Web API Request that App Router handlers receive (NextRequest satisfies it unchanged):
import { requireAgentAccount } from "@agentonboard/sdk/next";
// Simulated database lookup
const accounts = new Map([["ada@example.com", "user_123"]]);
export async function POST(request: Request) {
const session = await requireAgentAccount(request, {
audience: "notes.com",
// `email` is the verified identity claim. Normalize once.
resolveAccount: async ({ email }) => {
return accounts.get(email.toLowerCase()) ?? null;
},
});
// Automatically returns 401 (invalid/expired) or 403 (ACCOUNT_REQUIRED)
if (!session.ok) return session.response;
// `session.user` is your own user id. Scope your mutations with confidence.
return Response.json({ success: true, created_by: session.user });
}Call in route handlers, not in middleware
Next.js middleware cannot pass a resolved user record down to a route handler without writing it into request headers or cookies that a client could spoof. A middleware token check alone is not authorization. Always execute requireAgentAccount directly inside your route handler where queries are scoped.
3. How errors are handled
When token verification fails or the user is not found in your database, session.ok is false and session.response is a ready-to-return WHATWG Response:
401 Unauthorized: Token is missing, expired, signed by an unknown key, or minted for the wrong audience domain.403 Forbidden: Token is cryptographically valid, butresolveAccountreturnednull(ACCOUNT_REQUIRED).500 Internal Server Error:resolveAccountthrew an unhandled exception or options were invalid.
Custom failure responses
If you want custom headers or response formats (for example, providing a registration link on ACCOUNT_REQUIRED):
const session = await requireAgentAccount(request, {
audience: "notes.com",
resolveAccount,
onFailure: ({ code, error }) =>
code === "ACCOUNT_REQUIRED"
? Response.json(
{ problem: "connect_account", error },
{ status: 403, headers: { Location: "https://notes.com/signup" } }
)
: Response.json({ code, error }, { status: 401 }),
});4. Test with curl
Mint a 5-minute test token and verify your Next.js route:
# Mint a scoped test token
TOKEN=$(curl -s -X POST https://agentonboard.xyz/api/v1/token/exchange \
-H "Authorization: Bearer aon_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"audience": "notes.com"}' | jq -r .token)
# Call your local or deployed endpoint
curl -i -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/notes