Agent Auth plugin overview and purpose
The Agent Auth plugin lets a Better Auth server act as an Agent Auth provider implementing the Agent Auth Protocol. It gives AI agents a standard way to discover your service, register themselves, request approval, and execute scoped capabilities using short-lived signed JWTs. The plugin comes with adapters for OpenAPI and MCP so you can turn an existing REST API or MCP server into an agent-auth-enabled service without writing capabilities by hand.
Agent Auth plugin features
The Agent Auth plugin provides: OpenAPI adapter to derive capabilities, input/output schemas, and proxy onExecute handler from OpenAPI 3.x spec; MCP adapter to expose agent auth as MCP tools; discovery document at /.well-known/agent-configuration; capability listing, description, and execution with optional per-capability location URLs; delegated and autonomous agent modes; device authorization and CIBA approval flows; short-lived signed JWTs with replay protection; audit/event hooks for approvals, grants, and execution.
Agent Auth plugin installation packages
Install the main plugin with: npm install @better-auth/agent-auth. Optional client and CLI packages: npm install @auth/agent @auth/agent-cli
Agent Auth basic configuration example
To add Agent Auth plugin to Better Auth:
import { betterAuth } from "better-auth";
import { agentAuth } from "@better-auth/agent-auth";
export const auth = betterAuth({
plugins: [
agentAuth({
providerName: "Acme",
providerDescription: "Acme project and deployment APIs for AI agents.",
modes: ["delegated", "autonomous"],
capabilities: [
{
name: "deploy_project",
description: "Deploy a project to production.",
input: {
type: "object",
properties: {
projectId: { type: "string" },
},
required: ["projectId"],
},
},
{
name: "list_projects",
description: "List projects the current user can access.",
},
],
async onExecute({ capability, arguments: args, agentSession }) {
switch (capability) {
case "list_projects":
return [{ id: "proj_123", name: "marketing-site" }];
case "deploy_project":
return {
ok: true,
projectId: args?.projectId,
requestedBy: agentSession.user.id,
};
default:
throw new Error(`Unsupported capability: ${capability}`);
}
},
}),
],
});
Expose Agent Auth discovery document
The plugin provides auth.api.getAgentConfiguration(), which should be exposed from your app root at /.well-known/agent-configuration. The discovery route should live at /.well-known/agent-configuration, even if your Better Auth base path is /api/auth. Example:
import { auth } from "@/lib/auth";
import { NextResponse } from "next/server";
export async function GET() {
const configuration = await auth.api.getAgentConfiguration();
return NextResponse.json(configuration);
}
Agent Auth flow overview
The Agent Auth flow works as follows: 1) An agent discovers your provider from /.well-known/agent-configuration. 2) The agent lists capabilities and decides what it needs. 3) The agent registers with your server and requests capability grants. 4) Your user approves the request through device authorization or CIBA. 5) The agent signs short-lived JWTs with an aud that matches the URL it calls and invokes each granted capability at default_location or at that capability's own location if set.
Agent Auth discovery document fields
Important fields in the discovery document for execution: issuer - The provider's base URL (Better Auth baseURL). endpoints - Absolute URLs for each route (execute points at POST /capability/execute). default_location - The full URL of the default execute endpoint, always matching endpoints.execute. Agents use this as the JWT aud when a capability does not define a custom URL, and as the request URL for those capabilities.
OpenAPI adapter for Agent Auth
The createFromOpenAPI function reads an OpenAPI spec and produces everything the Agent Auth plugin needs: capabilities (one per operationId), input/output JSON Schemas, a proxy onExecute handler, and optionally providerName and providerDescription from info. Every operation with an operationId becomes a capability whose name is that id. Path, query, and header parameters plus the JSON request body are merged into a single input schema, and the 200/201 response body becomes output.
import { betterAuth } from "better-auth";
import { agentAuth } from "@better-auth/agent-auth";
import { createFromOpenAPI } from "@better-auth/agent-auth/openapi";
const spec = await fetch("https://api.example.com/openapi.json").then((r) => r.json());
export const auth = betterAuth({
plugins: [
agentAuth({
...createFromOpenAPI(spec, {
baseUrl: "https://api.example.com",
}),
}),
],
});
OpenAPI adapter upstream authentication
Use resolveHeaders in createFromOpenAPI to inject credentials that each upstream API request needs (such as an internal service token or a user-scoped access token looked up from agentSession).
Example:
createFromOpenAPI(spec, {
baseUrl: "https://api.example.com",
async resolveHeaders({ agentSession }) {
const token = await getAccessToken(agentSession.user.id);
return { Authorization: `Bearer ${token}` };
},
});
OpenAPI adapter default host capabilities
Control which capabilities are auto-granted to new hosts using defaultHostCapabilities in createFromOpenAPI. You can pass true (all), a single HTTP method string, an array of methods, or a callback that receives the full runtime context.
Example:
createFromOpenAPI(spec, {
baseUrl: "https://api.example.com",
defaultHostCapabilities: ["GET", "HEAD"],
});
OpenAPI adapter approval strength per HTTP method
Map HTTP methods to approvalStrength so mutating operations require stronger user verification (such as WebAuthn) while reads use a normal session.
Example:
createFromOpenAPI(spec, {
baseUrl: "https://api.example.com",
approvalStrength: {
GET: "session",
POST: "webauthn",
PUT: "webauthn",
DELETE: "webauthn",
},
});
OpenAPI adapter per-capability location
When you set location in createFromOpenAPI, every derived capability gets that URL. Agents call it directly with the agent JWT instead of going through the default execute endpoint. This is useful when you want the agent to hit the real API URL and handle the session in your own middleware rather than proxying through onExecute.
Example:
createFromOpenAPI(spec, {
baseUrl: "https://api.example.com",
location: "https://api.example.com/agent/execute",
});
OpenAPI adapter individual helper functions
The OpenAPI adapter exports lower-level helpers for individual use: fromOpenAPI(spec) - returns Capability[] only without handler or host caps. createOpenAPIHandler(spec, opts) - returns only the onExecute proxy handler so you can pair it with hand-written capabilities or filter the spec yourself.
Example:
import {
fromOpenAPI,
createOpenAPIHandler,
} from "@better-auth/agent-auth/openapi";
const capabilities = fromOpenAPI(spec);
const onExecute = createOpenAPIHandler(spec, {
baseUrl: "https://api.example.com",
});
agentAuth({ capabilities, onExecute });
Agent Auth capabilities definition
Capabilities are the contract between your application and an agent. Each capability has a name, a description, and optionally a JSON Schema input definition. By default, agents call default_location from discovery (the execute URL) and the plugin runs onExecute. If you set location on a capability, agents call that absolute URL instead and onExecute is not used for those requests. You must resolve the agent session with helpers and implement the handler yourself.
Define Agent Auth capabilities
Example of defining capabilities in Agent Auth plugin:
agentAuth({
capabilities: [
{
name: "create_issue",
description: "Create an issue in the current workspace.",
input: {
type: "object",
properties: {
title: { type: "string" },
body: { type: "string" },
},
required: ["title"],
},
},
],
});
Agent Auth capability with custom location
Optional location field on capabilities - agents call this URL with the agent JWT instead of the default execute URL.
Example:
{
name: "create_issue",
description: "Create an issue in the current workspace.",
location: "https://api.example.com/v1/issues",
}
Agent Auth default execute vs custom location
Two execution paths in Agent Auth: No location - Agents POST to default_location (endpoints.execute) with { capability, arguments }. After the plugin validates the JWT and grant, it runs onExecute. With location - Agents call that URL (your REST handler, another service, an OpenAPI operation URL, etc.). onExecute does not run for that call. You must resolve agentSession in your handler using the helpers and enforce grants and business logic.
Get Agent Auth session from request headers
For custom location routes or any non-execute handler, the agent sends an Authorization: Bearer header with the agent JWT. Use auth.api.getAgentSession({ headers }) to run JWT verification in-process and return AgentSession or null. Verification includes signature, aud, replay (jti), expiry, and (when present) request-binding claims.
Example:
import { auth } from "@/lib/auth";
export async function POST(request: Request) {
const agentSession = await auth.api.getAgentSession({
headers: request.headers,
});
if (!agentSession) {
return new Response("Unauthorized", { status: 401 });
}
// Check grants, enforce constraints, run your handler…
}
Agent Auth verifyAgentRequest helper
Alternatively use verifyAgentRequest(request, auth) which does the same verification by forwarding the Request's headers to GET /agent/session via auth.handler. Pick whichever fits your code shape.
import { verifyAgentRequest } from "@better-auth/agent-auth";
const agentSession = await verifyAgentRequest(request, auth);
Check Agent Auth grants in custom location handler
After you have agentSession, inspect agentSession.agent.capabilityGrants. These are active DB grants intersected with the JWT's capabilities claim. For the capability this route implements, ensure there is a matching grant.
Example:
const CAP = "create_issue";
const allowed = agentSession.agent.capabilityGrants.some(
(g) => g.capability === CAP && g.status === "active",
);
if (!allowed) {
return new Response("Forbidden", { status: 403 });
}
Agent Auth grant constraints validation
If a grant has constraints, validate the request body or query the same way POST /capability/execute would. Otherwise a client could bypass constraints by calling your custom URL. The plugin does not re-run execute's constraint helpers on arbitrary routes; that logic stays in your handler or in shared code extracted from your onExecute path.
Agent Auth session properties
AgentSession contains: agentSession.user - Resolved user for the agent (delegated host user or resolveAutonomousUser). agentSession.agent - Id, name, mode, capabilityGrants, host id, metadata. agentSession.host - Host record when the agent is linked to a host. Types are exported from @better-auth/agent-auth.
Agent Auth JWT audience validation
The JWT aud must match what the server expects for the URL being called. No per-capability location - Use default_location or endpoints.execute or issuer/base URL values the plugin already allows. With location - aud should be that same absolute URL. GET /capability/list includes location when set. Invalid location values in config fail at startup. Single capability in JWT - If capabilities lists exactly one id, aud may equal that capability's location when set. Multiple capabilities in JWT - Per-capability location values are not accepted as aud; use issuer, base path, or default execute endpoint instead. Behind a reverse proxy, set trustProxy if you need Host/X-Forwarded-Proto to line up with aud validation.
Filter visible Agent Auth capabilities
Use resolveCapabilities to show different capability sets to different callers, such as plan-gated, user-specific, or organization-specific capabilities.
Agent Auth onExecute handler
The onExecute handler runs for capabilities that use the default execute URL (no per-capability location). The plugin verifies the JWT (including aud), attaches agentSession, checks the grant, then calls onExecute. Capabilities with a custom location never hit this path - you handle them in your own route using the session helpers.
Example:
agentAuth({
capabilities: [
{
name: "create_issue",
description: "Create an issue in the current workspace.",
},
],
async onExecute({ capability, arguments: args, agentSession }) {
if (capability !== "create_issue") {
throw new Error("Unsupported capability");
}
return {
ok: true,
title: args?.title,
createdBy: agentSession.user.id,
};
},
});
Agent Auth approval flows
The Agent Auth plugin supports two approval methods: device_authorization for browser-based approval with a user code and ciba for backchannel approval flows. By default, both are enabled.
Configure Agent Auth approval methods
Configure approval methods and resolution in Agent Auth plugin:
agentAuth({
approvalMethods: ["ciba", "device_authorization"],
resolveApprovalMethod: ({ preferredMethod, supportedMethods }) => {
if (preferredMethod && supportedMethods.includes(preferredMethod)) {
return preferredMethod;
}
return "device_authorization";
},
deviceAuthorizationPage: "/device/capabilities",
});
Agent Auth device approval UI requirement
The Agent Auth plugin does not render the device approval UI. Your app must provide the page referenced by deviceAuthorizationPage.
Agent Auth events and auditing
Use onEvent in Agent Auth plugin to capture important lifecycle events such as agent creation and revocation, host creation and enrollment, capability requests and approvals, and capability execution. This hook is a good place to write audit logs or feed analytics pipelines.
Agent Auth plugin configuration options
Agent Auth plugin configuration options:
providerName (string, optional) - Human-readable provider name returned in discovery metadata.
providerDescription (string, optional) - Description returned in the discovery document.
modes (("delegated" | "autonomous")[], optional) - Supported agent modes. Defaults to ["delegated", "autonomous"].
capabilities (Capability[], optional) - Capability definitions (name, description, optional input, optional absolute location — if set, agents call this URL instead of default_location and you use session helpers in your handler).
onExecute (function, optional) - Handler for capabilities invoked via the default execute URL (default_location). Not called when the agent uses a custom per-capability location.
requireAuthForCapabilities (boolean, optional) - Require a host or agent JWT to list and describe capabilities.
approvalMethods (string[], optional) - Supported approval methods. Defaults to ["ciba", "device_authorization"].
resolveApprovalMethod (function, optional) - Choose the approval method for a request.
deviceAuthorizationPage (string, optional) - Path or absolute URL for the user-facing device approval page.
defaultHostCapabilities (string[] | function, optional) - Default capabilities applied to newly created hosts.
allowDynamicHostRegistration (boolean | function, optional) - Allow unknown hosts to register dynamically.
onEvent (function, optional) - Callback for audit and lifecycle events.
trustProxy (boolean, optional) - Trust X-Forwarded-Proto when validating JWT aud against request host (use behind a reverse proxy). Defaults to false.
Agent Auth plugin stability warning
The Agent Auth plugin is an implementation of a standard on heavy development. It is not yet stable and may change in the future. Issues or bugs should be reported on Github at https://github.com/better-auth/agent-auth
plugins option
The plugins option accepts a list of Better Auth plugins. Plugins can be imported from 'better-auth/plugins' and configured with their respective options.