Skip to main content
A theAuth app has one loop: a user signs in, creates agents, agents call authorize() before acting, and every decision lands in the audit trail. Everything else is detail on top of that loop.

User vs agent

A user is a human. They have email, password, sessions, OAuth accounts. theAuth can run human auth for you (see Authentication) or plug into Clerk, Auth.js, better-auth. An agent is a program acting on a user’s behalf. One human can own many agents. Agents do not sign in. They authenticate with a bearer token (kv_...) that is issued once and hashed at rest. No password, no session, no OAuth.
If you reach for password reset, email verification, or social sign-in on an agent, you want a user, not an agent. Agents are non-interactive by design.

Permission model

Permissions are strings that describe what the agent may do, scoped to resources it may touch.
Three shapes carry most of the real work: the resource string (free-form, convention-driven, usually kind:namespace:id), the action list (read, write, execute, domain-specific verbs), and constraints (rate, approval, IP, time, argument patterns). authorize() evaluates all three. allowedArgPatterns are regular expressions, and every string argument must match every pattern.
Resource strings are conventions, not enforced syntax. Pick mcp:github:*, db:users:write, s3:bucket:objects, whatever reads in logs. Consistency matters more than syntax.

Delegation

An agent can hand a subset of its permissions to a sub-agent. Every hop carries a depth counter, an expiry, and can be revoked independently. Revoking a delegation cascades: every delegation made onward from that link is revoked too, so those agents lose the delegated permissions the next time they call authorize().
An agent cannot delegate permissions it does not hold. Attempts to escalate are rejected at delegation time, not at the next authorize() call. maxDepth limits chain length: the new link’s depth must be less than or equal to it (default 3).

Audit trail

When agents.auditAll is on (the default), every authorize() and authorizeByToken() call against a known agent writes one row: The IP address and user agent are stored too, when the request carried them. Nothing in the API edits entries; theauth.audit.cleanup({ retentionDays }) deletes entries older than the retention window. Export as JSON or CSV for compliance.

Agent types

Acts on its own. No human approval unless a permission constraint says so. Background jobs, cron, assistants that run unattended.
Gets permissions from a parent agent via delegation. Use for ephemeral sub-agents created to finish one task, then discarded.
Long-lived infrastructure identity. MCP servers, internal microservices, anything service-account-shaped.

Trust scoring

Every agent can have a score from 0 to 100, computed from its audit log when you call theauth.trust.computeScore(agentId) (it is not recomputed on each authorize() call). The score starts at 50 and moves with successful calls (up), denials (down), flagged denials (down more), and agent age (up). The score maps to a level: untrusted (below 40), limited (40-59), standard (60-79), trusted (80-94), elevated (95+). See Trust scoring for the full formula and Policy templates for policies you can pair with the level.

MCP OAuth

theAuth ships an OAuth 2.1 authorization server for Model Context Protocol tool servers. Your MCP servers register as OAuth clients, and they get scoped JWT access tokens. The OAuth server comes from createMcpModule in @glinr/theauth/mcp; token validation is separate from agent authorize() and does not write audit entries. Three relevant standards: PKCE S256 (RFC 7636), Protected Resource Metadata (RFC 9728), Dynamic Client Registration (RFC 7591). Full details in MCP OAuth 2.1.

Quickstart

Five minutes to a working agent.

Add to an existing app

Drop theAuth next to Clerk, Auth.js, or better-auth.

Permissions

Resource strings, constraints, approval gates.

Delegation

Chains, depth limits, revocation.
Last modified on October 7, 2026