AgentOnboard Docs

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/sdk

2. Guard your route handler

requireAgentAccount(request, options) accepts the standard Web API Request that App Router handlers receive (NextRequest satisfies it unchanged):

app/api/notes/route.ts
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, but resolveAccount returned null (ACCOUNT_REQUIRED).
  • 500 Internal Server Error: resolveAccount threw 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

On this page