Skip to main content
@better-auth/agent-auth is better-auth’s attempt to add AI-agent support alongside a human-first auth library. It is marked “heavy development, not yet stable” in their repo as of 2026-03. If you started there to get agent identity with the broader better-auth feature set, moving to theAuth gives you a single codebase where agents are a first-class entity instead of a separate package bolted onto the user model. Both libraries share the same ancestor on the human-auth side. Most of the familiar hooks keep their names. The agent story is the part that changes materially. If you use the human-auth half of better-auth and not the agent plugin yet, read the general better-auth migration guide first, this page only covers the agent-plugin deltas.

Where the model diverges

better-auth treats an agent as an OAuth client attached to a user. A single-hop delegation is possible. There is no delegation chain tracking, no trust score, no cost attribution, no ephemeral session type, and no MCP OAuth 2.1 authorization server built in. theAuth treats an agent as a primary database entity with its own lifecycle:
  • AgentIdentity row in the schema, owned by a user, with status transitions (active, revoked, expired).
  • Multi-hop delegation chains with a per-call depth limit and cascading revocation of chains.
  • Trust scoring, 5 levels, computed from the audit log.
  • Cost attribution per agent, tool, and chain.
  • An MCP OAuth 2.1 authorization server (createMcpModule) that sits alongside the same instance and adapter.
  • Ephemeral agent sessions for one-off tasks (createEphemeralSessionModule).

Concepts map

Server setup

theAuth has no emailAndPassword config key, its built-in password module authenticates by username (see Username and password). The rest of the human-auth config (organization, twoFactor, etc.) keeps a similar plugin shape, imported from @glinr/theauth/auth rather than @glinr/theauth/plugins. Check each feature’s page, the option names are not identical.

Creating an agent

The shape of the call is close. The substantive difference is the permission model. A scopes: ['github:read'] list maps to { resource: 'mcp:github:*', actions: ['read'] }. The extra structure pays for itself when you need to add rate caps, argument allowlists, or time windows. In a resource pattern, * matches the rest of the path from that position on, so mcp:github:* also covers mcp:github:repos:list. ownerId must be the id of an existing user (theauth_users).

Authorizing a tool call

One call. One audit row. One place to reason about rate caps and constraints.

Delegation

The delegated permissions must be a subset of the parent agent’s own permissions, otherwise delegate throws. expiresAt is required. Revocation works on chains, not on the parent agent. theauth.agent.revoke(parent.id) marks that agent revoked so its own calls are denied, but it does not touch the chains it already issued. Call theauth.delegation.revoke(chain.id) to cut the child off, which also revokes the chains the child delegated onward. Use theauth.delegation.listChains(agentId) to find them. maxDepth is checked on each call to theauth.delegate: the new link’s depth must be less than or equal to the maxDepth you pass on that call (default 3). Depth is one more than the deepest active chain that ends at fromAgent. So maxDepth: 2 stops a third hop only if you pass it when creating that third link. Nothing stores a root-level cap that later calls inherit, so pass the same value from your delegation helper every time.

Trust scoring

@better-auth/agent-auth does not ship scoring. theAuth computes a 0-100 score from the audit history, mapped to five named levels. Useful as a gate for sensitive actions that should only run for agents with a clean track record.

MCP server

If you were running a separate MCP OAuth server alongside better-auth (because the @better-auth/mcp plugin is a thin OIDC wrapper without agent semantics), you can retire it. theAuth’s OAuth 2.1 server is a module you create with createMcpModule from @glinr/theauth/mcp and pass to your framework adapter. It does not take the theauth instance, and it has no built-in database: you supply the storage callbacks. MCP has a complete in-memory example of those callbacks.
mcpStore supplies storeClient, findClient, storeAuthorizationCode, consumeAuthorizationCode, storeToken, findTokenByRefreshToken, revokeToken, and resolveUserId. The adapter serves /mcp/register, /mcp/authorize, and /mcp/token, plus the two .well-known documents, all relative to its mount path (/api/theauth by default). MCP clients look for the .well-known documents at the root of your origin, so add root-level routes or rewrites when you mount under a prefix, see MCP.

Data migration

Agent rows in better-auth live in whatever table the plugin writes to (check the plugin’s schema migration). theAuth stores agents in theauth_agents and their permissions in theauth_permissions, and it keeps only the SHA-256 hash of the full kv_... token. A better-auth token hash cannot be reused: the bearer would have to be a kv_ token whose SHA-256 matches, which no old token is. So do not copy token hashes with SQL. Recreate each agent through the API and hand the owner a new token:
The scope mapping above is only a starting point. Decide per scope what resource pattern and actions it should map to, and test the result in staging, the mapping is the load-bearing piece.

Cutover plan

  1. Stand up theAuth next to better-auth against the same database (or a shadow copy). Do not delete the better-auth schema yet.
  2. Import agents with theauth.agent.create. Validate one new service token with theauth.authorizeByToken(token, {...}).
  3. Give a single non-critical service its new token and switch it to theauth.authorizeByToken instead of auth.api.verifyAgentToken.
  4. Watch the audit log for allowed: false entries. Fix permission mappings.
  5. Expand the theAuth-guarded agent surface one service at a time.
  6. Remove the @better-auth/agent-auth plugin from lib/auth.ts once no service still calls it.
The human-auth side can move in the same PR or stay on better-auth during the transition, see the general better-auth migration guide.

Rollback

Keep the better-auth agent plugin wired for at least one rotation cycle after the cut-over. If you need to revert:
  1. Re-enable the agent() plugin in lib/auth.ts.
  2. Point services back at auth.api.verifyAgentToken.
  3. theAuth agent rows remain, they just stop being read. Old better-auth bearers keep working on better-auth because you never revoked them there, new kv_ tokens only work against theAuth.

Runnable example

examples/migrate-from-better-auth-agent-plugin runs the AFTER patterns end-to-end: AgentIdentity creation, multi-hop delegation with maxDepth, authorize-falls-back-to-chain semantics, and cascading chain revocation. The script uses the monorepo workspace:* version so it tracks the released package, and a vitest smoke suite runs it in CI.

Next steps

Agent identity

The primary entity model and lifecycle.

Delegation chains

Multi-hop delegation with depth and cascading revocation.

Trust scoring

Graduated autonomy by audit history.

MCP OAuth 2.1

The authorization server the better-auth plugin does not ship.
Last modified on October 7, 2026