# Authentication for AI Applications - Part 2

> Part 2 of 2. Start with [Authentication for AI Applications](https://clerk.com/articles/authentication-for-ai-applications.md).

In [Part 1](https://clerk.com/articles/authentication-for-ai-applications.md), we examined the unique challenges of AI identity, comparing user-delegated and autonomous agent patterns and defining how to limit blast radii via token scoping and robust delegation architectures. Here in Part 2, we turn to the system level. We will explore how to enforce rigorous multi-tenant isolation, the protocols driving external agent integration like MCP and OAuth 2.1, and the security checks demanded by the OWASP Top 10 for Agentic Applications. Finally, we provide a complete implementation guide for securing AI agents using Clerk and Next.js 16.

## Multi-Tenant AI Architecture

Multi-tenant AI architecture isolates each user's and organization's data from every other tenant, even when agents share compute, caches, and model context. One user's agent must not read another user's data; one organization's agent must not cross into another organization's data; and prompt injection or state bleeding must never let an agent act under the wrong principal. This section covers per-user isolation, organization scope, boundary enforcement, and the leak vectors unique to AI.

### Per-User Agent Isolation

An agent bound to a single user session must only see that user's data. Two failure modes dominate: **stale binding** (an agent initialized for user A serves a request from user B because the session changed mid-flight) and **prompt injection** (external content tricks the agent into calling a tool with another user's identifier — [Unit 42](https://unit42.paloaltonetworks.com/ai-agent-prompt-injection/)). [LayerX's leakage analysis](https://layerxsecurity.com/generative-ai/multi-tenant-ai-leakage/) names five identities the server must keep straight: trigger, execution, authorization, tenant, and attribution.

Every agent credential should carry `sub` and a session identifier. Middleware rejects tokens where `sub` does not match the session cookie subject. Revoke agent credentials on sign-out via the Clerk `session.ended` webhook ([Clerk Webhooks](https://clerk.com/docs/guides/development/webhooks/overview.md)). [Scalekit](https://www.scalekit.com/blog/access-control-multi-tenant-ai-agents) catalogs three isolation layers — config isolation, named connection binding, and code boundaries — that map onto the middleware.

### Organization-Scoped AI Agents

Some agents belong to an organization, not a user — e.g., a marketing assistant for Acme Corp that runs for the whole team. Org-scoped tokens must carry `org_id` and `org_role` claims, verified on every request. [Clerk Organizations](https://clerk.com/docs/guides/organizations/overview.md) provide the multi-tenant primitive and expose org claims in every session JWT; background agents must pass the session token in the `Authorization` header (not cookies) to apply the correct org context.

Role-based access control ([RBAC](https://clerk.com/glossary/role-based-access-control-rbac.md)) for agents mirrors RBAC for humans. Clerk ships `org:admin` and `org:member` defaults with up to 10 [custom roles](https://clerk.com/glossary/custom-roles.md) per application instance, [custom permissions](https://clerk.com/glossary/custom-permissions.md) in the format `org:<feature>:<permission>` ([Clerk Org Roles](https://clerk.com/docs/guides/organizations/control-access/roles-and-permissions.md)), and full BAPI CRUD over roles and permissions ([changelog](https://clerk.com/changelog/2025-11-24-organization-roles-and-permission-bapi-management.md)). System permissions do not appear in session claims — check them explicitly with `has()`.

### Tenant Boundary Enforcement

Every agent request MUST carry an `org_id` (or tenant-equivalent) claim. Middleware extracts it, compares against the requested resource, and rejects mismatches with a 403 + structured error body (see [Structured Auth Error Responses for AI Agents](#structured-auth-error-responses-for-ai-agents)). A single missing check on a single endpoint is sufficient for cross-tenant leakage — there is no optimization that justifies skipping it.

In Next.js 16, edge routing protection lives in `proxy.ts` (`middleware.ts` is deprecated). The per-request token check runs inside the route handler via `auth({ acceptsToken: [...] })`, accepts session / OAuth / M2M / API-key tokens, and enforces the tenant boundary. The full real-TypeScript example lives in [Step 5: Enforce Multi-Tenant Isolation on Every Request](#step-5-enforce-multi-tenant-isolation-on-every-request). 403 responses must use the agent-parseable shape — `application/problem+json` ([RFC 9457](https://www.rfc-editor.org/rfc/rfc9457)) — so a misrouted agent can fail gracefully.

### Preventing Cross-Tenant Data Leakage from Agents

LayerX identifies five leak vectors specific to multi-tenant AI: **context-window bleeding** (a prior user's messages remain in model context), **KV-cache side channels** (shared inference caches leak across tenants), **state management failures** (per-request state rebinds to the wrong tenant), **broad unscoped queries** (no tenant filter), and **shared encryption keys** (one compromise cascades). Mitigate with a separate agent context per tenant, tenant-keyed database queries enforced at the RLS layer (see [Delegation Pattern: Agent Performing Database Operations](https://clerk.com/articles/authentication-for-ai-applications.md#delegation-pattern-agent-performing-database-operations)), tenant-aware caches, and per-tenant encryption keys. Palo Alto Unit 42's ["Double Agents" research](https://unit42.paloaltonetworks.com/double-agents-vertex-ai/) on Vertex AI shows how a metadata-service leak can defeat every application-layer check — the database and cache layers must independently enforce tenancy.

## Authenticating MCP Servers and Modern AI Protocols

The [Model Context Protocol](https://modelcontextprotocol.io/) is the dominant external-agent surface in 2026: **97 million+ monthly SDK downloads** as of Anthropic's December 2025 donation announcement ([Anthropic](https://www.anthropic.com/news/donating-the-model-context-protocol-and-establishing-of-the-agentic-ai-foundation)) and **10,000+ active public MCP servers** ([MCP Manager](https://mcpmanager.ai/blog/mcp-adoption-statistics/)). This section covers the protocol, the OAuth 2.1 + PKCE authorization flow, the Dynamic Client Registration (DCR) and Client ID Metadata Documents (CIMD) client-identification standards, and the host-side requirements for exposing a SaaS application safely.

### What Is the Model Context Protocol (MCP)?

MCP is an open JSON-RPC 2.0 protocol that lets AI tools (Claude, ChatGPT, Cursor, VS Code, Copilot) invoke tools on remote servers. It defines a host / client / server triad with primitives (Tools, Resources, Prompts) and two transports: **stdio** (local subprocess) and **Streamable HTTP** (remote, single endpoint, mandatory `Origin` validation for local servers) ([MCP Architecture](https://modelcontextprotocol.io/docs/concepts/architecture), [MCP Transports](https://modelcontextprotocol.io/docs/concepts/transports)). Anthropic [donated MCP to the Linux Foundation's Agentic AI Foundation on December 9, 2025](https://www.anthropic.com/news/donating-the-model-context-protocol-and-establishing-of-the-agentic-ai-foundation).

### OAuth 2.1 Authorization for MCP Servers

Streamable HTTP MCP servers SHOULD use OAuth 2.1 + [PKCE](https://clerk.com/glossary/pkce.md). The discovery + auth sequence:

1. Client sends an unauthenticated request; server returns `401` with `WWW-Authenticate: Bearer resource_metadata="<url>"`.
2. Client fetches the Protected Resource Metadata (PRM) document at `/.well-known/oauth-protected-resource` ([RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)) and the Authorization Server (AS) metadata at `/.well-known/oauth-authorization-server` ([RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414)).
3. Client registers (via DCR, see [Dynamic Client Registration for AI Tools](#dynamic-client-registration-for-ai-tools)) or identifies itself (via CIMD), runs Authorization Code + PKCE, and presents the access token as `Authorization: Bearer` on every JSON-RPC request.

The JWT **audience must be validated** on every request ([MCP Authorization Tutorial](https://modelcontextprotocol.io/docs/tutorials/security/authorization)); tokens must never be logged. Aaron Parecki's ["Let's Fix OAuth in MCP"](https://aaronparecki.com/2025/04/03/15/oauth-for-model-context-protocol) is the foundational critique that shaped the current MCP spec.

### Dynamic Client Registration for AI Tools

With N AI clients × M MCP servers, manual registration is impractical. Two solutions: **[Dynamic Client Registration](https://clerk.com/glossary/dynamic-client-registration.md)** ([RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591)) where clients self-register at runtime, and **Client ID Metadata Documents (CIMD)** — the Nov 2025 evolution where clients identify via a URL they control for DNS-based trust ([Aaron Parecki summary](https://aaronparecki.com/2025/11/25/1/mcp-authorization-spec-update)). Expect variance: GitHub's MCP server [does not support DCR](https://github.blog/ai-and-ml/generative-ai/a-practical-guide-on-how-to-use-the-github-mcp-server/). Clerk exposes DCR as a Dashboard toggle under [OAuth applications → Settings](https://dashboard.clerk.com/~/oauth-applications?oauth_applications_tab=settings) ([changelog](https://clerk.com/changelog/2025-06-13-oauth-improvements.md)).

### Connecting an MCP Server to a SaaS Application Safely

Host-side requirements: expose PRM (`/.well-known/oauth-protected-resource`) and AS metadata (`/.well-known/oauth-authorization-server`); enforce audience binding, short TTLs, least-privilege scopes, and PKCE on every flow; restrict the `Origin` header on local servers; and for enterprise deployments support **ID-JAG** / Enterprise-Managed Authorization ([MCP extension](https://modelcontextprotocol.io/extensions/auth/enterprise-managed-authorization)) so a central IdP can federate agents across all your MCP servers.

For **Next.js**, Clerk's primitive is `verifyClerkToken` (from `@clerk/mcp-tools/next`), passed as the verifier callback into Vercel's `withMcpAuth` wrapper (from `mcp-handler` — not a Clerk export). The canonical PRM path via `resourceMetadataPath` is `/.well-known/oauth-protected-resource/mcp` ([Clerk MCP Next.js guide](https://clerk.com/docs/nextjs/guides/ai/mcp/build-mcp-server.md)). For **Express**, Clerk ships `mcpAuthClerk` middleware (from `@clerk/mcp-tools/express`) plus `protectedResourceHandlerClerk`, `authServerMetadataHandlerClerk`, and `streamableHttpHandler` — a one-import pattern ([Clerk MCP Express guide](https://clerk.com/docs/expressjs/guides/ai/mcp/build-mcp-server.md)). Clerk also hosts a docs/snippets MCP server at `https://mcp.clerk.com/mcp` exposing `clerk_sdk_snippet` and `list_clerk_sdk_snippets` to AI assistants ([Clerk MCP server guide](https://clerk.com/docs/guides/ai/mcp/clerk-mcp-server.md), [changelog](https://clerk.com/changelog/2026-01-20-clerk-mcp-server.md)) — a documentation tool, not an auth proxy. The endpoint is a Streamable HTTP MCP surface (not a browser URL); wire it into your AI assistant's MCP config rather than visiting it directly.

## Security Considerations for Agentic AI

Agentic AI security is the set of authentication, authorization, and audit controls that prevent AI agents from exceeding their intended authority, leaking tenant data, or being hijacked by prompt injection. It matters because **53% of organizations reported AI agents exceeding permissions in the past year** ([CSA, April 2026](https://cloudsecurityalliance.org/press-releases/2026/04/16/more-than-half-of-organizations-experience-ai-agent-scope-violations-cloud-security-alliance-study-finds)) and **88% confirmed or suspected security incidents involving AI agents** ([Gravitee, Feb 2026](https://www.gravitee.io/blog/state-of-ai-agent-security-2026-report-when-adoption-outpaces-control)). The [OWASP Top 10 for Agentic Applications 2026](https://genai.owasp.org/2025/12/09/owasp-top-10-for-agentic-applications-the-benchmark-for-agentic-security-in-the-age-of-autonomous-ai/) enumerates ten risk classes; this section covers the seven defense layers every production AI app needs.

### Least-Privilege by Default

Start each agent with **zero standing access**. Grant narrow, time-bound permissions per task — the Task-Based Authorization pattern documented in [OpenFGA's agent guide](https://openfga.dev/docs/modeling/agents) and [Oso's agent best practices](https://www.osohq.com/learn/best-practices-of-authorizing-ai-agents). Replace long-lived service-account credentials with task-scoped tokens; [Auth0's guide to mitigating excessive agency](https://auth0.com/blog/mitigate-excessive-agency-ai-agents/) pairs this with CIBA (Client-Initiated Backchannel Authentication) for asynchronous human approval on critical actions.

### Prompt Injection and Authorization Boundaries

Prompt injection ([OWASP LLM01](https://genai.owasp.org/llmrisk/llm01-prompt-injection/)) is the #1 LLM vulnerability and **cannot be fully prevented at the model layer** — enforcement must happen in the auth and authorization boundary around the agent. Indirect injection hides payloads in RAG corpora, PDFs, emails, and MCP tool descriptions; [Lakera](https://www.lakera.ai/blog/indirect-prompt-injection) showed **5 crafted documents can manipulate AI responses 90% of the time**, and Unit 42 catalogued [12 production attack cases](https://unit42.paloaltonetworks.com/ai-agent-prompt-injection/) including database destruction. Defenses that hold: the agent never sees raw credentials (token vault, see [Storing and Refreshing Third-Party Tokens](https://clerk.com/articles/authentication-for-ai-applications.md#storing-and-refreshing-third-party-tokens)); tool calls are per-task scoped with FGA decisions server-side; human-in-the-loop via CIBA for high-risk actions ([Auth0](https://auth0.com/blog/secure-human-in-the-loop-interactions-for-ai-agents/)). Assume the model will be manipulated and let the auth layer refuse.

### Audit Logging for Non-Human Actors

AI agent [audit logs](https://clerk.com/glossary/audit-logs.md) must capture **intent**, not just state ([ISACA — Auditing Agentic AI](https://www.isaca.org/resources/news-and-trends/industry-news/2025/the-growing-challenge-of-auditing-agentic-ai)). Mandatory fields per [LoginRadius](https://www.loginradius.com/blog/engineering/auditing-and-logging-ai-agent-activity) and the [NIST AI RMF](https://www.nist.gov/itl/ai-risk-management-framework): `agent_id`, `parent_identity`, `delegation_scope`, `tool_name`, `tool_params_hash` (SHA-256 — never log raw params that may contain secrets), `policy_decision`, and `trace_id`.

Use structured JSON aligned to [OpenTelemetry GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-agent-spans/) (spans `create_agent`, `invoke_agent`, `execute_tool`), retain 1–7 years, and align to [ISO/IEC 42001](https://aws.amazon.com/blogs/security/ai-lifecycle-risk-management-iso-iec-420012023-for-ai-governance/). The [EU AI Act Article 12 logging requirements](https://www.helpnetsecurity.com/2026/04/16/eu-ai-act-logging-requirements/) mandate 6-month minimum retention for high-risk systems from August 2, 2026.

### Structured Auth Error Responses for AI Agents

Agents need **machine-parseable** failure responses so they can refresh, retry, or escalate without human intervention. Use [RFC 9457 Problem Details](https://www.rfc-editor.org/rfc/rfc9457) (`application/problem+json`) for the body, [RFC 6750 §3](https://www.rfc-editor.org/rfc/rfc6750#section-3) OAuth machine codes in both the body and `WWW-Authenticate`, and [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110)'s `Retry-After` / `RateLimit-*` headers on 429 / 503.

```json
{
  "type": "urn:error:insufficient_scope",
  "title": "Insufficient scope",
  "status": 403,
  "detail": "Token is missing scope 'crm.write:acme'",
  "error": "insufficient_scope",
  "required_scopes": ["crm.write:acme"]
}
```

RFC 9457 accepts any URI for `type` — a URN avoids implying an HTTP document exists and matches the helper in [Step 5: Enforce Multi-Tenant Isolation on Every Request](#step-5-enforce-multi-tenant-isolation-on-every-request) (`urn:error:${error}`).

Paired header: `WWW-Authenticate: Bearer error="insufficient_scope", scope="crm.write:acme"`.

Agent recovery matrix:

| HTTP | OAuth error                | Agent action                                                                    |
| ---- | -------------------------- | ------------------------------------------------------------------------------- |
| 401  | `invalid_token`            | Refresh or re-acquire. M2M: call `clerkClient.m2m.createToken` again.           |
| 403  | `insufficient_scope`       | Re-prompt for consent with `required_scopes`; headless agents queue for review. |
| 403  | `invalid_request` (tenant) | Fail, surface typed error to human review — never retry silently.               |
| 429  | —                          | Back off using the larger of `Retry-After` and jitter.                          |

### Revoking and Rotating Agent Credentials

Revoke on user sign-out, role change, detected compromise, time-based rotation, and end-of-task. Hard constraint: **opaque tokens revoke instantly; JWTs do not** — JWTs cannot be invalidated mid-TTL without a denylist or a short TTL you can tolerate waiting out. **91% of former-employee tokens remain active** ([NHIMG](https://nhimg.org/2025-state-of-non-human-identities-and-secrets-in-cybersecurity)); **64% of 2022-era secrets are still not revoked in 2026** ([GitGuardian](https://blog.gitguardian.com/the-state-of-secrets-sprawl-2026/)). Clerk's [token formats guide](https://clerk.com/docs/guides/development/machine-auth/token-formats.md) frames the tradeoff: JWT for performance, opaque for instant invalidation.

### Secret Storage for AI Agents

Agents must never hold long-lived static secrets. Prefer ephemeral credentials: SPIFFE SVIDs ([HashiCorp](https://www.hashicorp.com/en/blog/spiffe-securing-the-identity-of-agentic-ai-and-non-human-actors)), workload identities, or vault-issued short TTLs. **24,008 unique secrets** were found in MCP config files in year one, with **2,117 confirmed exploitable** ([GitGuardian, 2026](https://blog.gitguardian.com/the-state-of-secrets-sprawl-2026/)).

> **MCP stdio arbitrary command execution (April 2026).** OX Security disclosed a systemic vulnerability in the MCP stdio transport enabling arbitrary OS command execution on \~**200,000 servers across 150M+ downloads**, with 10+ high/critical CVEs ([The Register](https://www.theregister.com/2026/04/16/anthropic_mcp_design_flaw/); [OX Security](https://www.ox.security/blog/the-mother-of-all-ai-supply-chains-critical-systemic-vulnerability-at-the-core-of-the-mcp/)). Anthropic declined to modify the protocol architecture. Streamable HTTP is not affected. **Production MCP servers should use Streamable HTTP, not stdio.**

### Rate Limiting and Abuse Prevention

Agents amplify request volume — 100× a human is normal. [Rate-limit](https://clerk.com/glossary/api-rate-limits.md) on **both** request count (RPM) and token count (input + output tokens per minute). A defensible ceiling is a major LLM API's own published limit: at Anthropic's entry **Start tier**, Claude Sonnet 4.x and Haiku 4.5 each allow **1,000 RPM, 2,000,000 input TPM, and 400,000 output TPM** ([Anthropic — API rate limits](https://platform.claude.com/docs/en/api/rate-limits)); limits scale up by tier and are subject to change.

| Workload                  | RPM                             | Input TPM         | Output TPM |
| ------------------------- | ------------------------------- | ----------------- | ---------- |
| Background / batch agents | 50–4,000                        | 30k–2M            | 8k–400k    |
| User-facing chat          | 500–10,000                      | 30k–800k combined | —          |
| Per-endpoint ceiling      | \~1,500 (AWS Bedrock AgentCore) | —                 | —          |
| Minimum viable floor      | 50                              | 30,000            | 8,000      |

Use a sliding-window algorithm for LLM traffic ([Zuplo](https://zuplo.com/learning-center/token-based-rate-limiting-ai-agents)), return structured 429 responses with `Retry-After` and `RateLimit-*` headers per RFC 9110.

### Shared Responsibility Between the Agent Framework and the Auth Layer

Draw a clear boundary: the **agent framework** (LangGraph, Vercel AI SDK, Mastra, Claude Agent SDK) handles tool invocation, prompt routing, and conversation state; the **auth layer** handles identity, token issuance, and verification. Never embed secrets in agent framework config — the auth layer injects scoped, short-lived credentials at call time. See [LangGraph custom auth](https://docs.langchain.com/langgraph-platform/custom-auth), [Mastra custom auth](https://mastra.ai/docs/server/auth/custom-auth-provider), [Vercel AI SDK 6](https://vercel.com/blog/ai-sdk-6), [Claude Agent SDK MCP](https://platform.claude.com/docs/en/agent-sdk/mcp), and the [Clerk Agent Toolkit](https://clerk.com/changelog/2025-03-7-clerk-agent-toolkit.md) for framework-side integration patterns.

## Implementation Guide: Wiring Up AI Agent Authentication

This section shows a working Next.js 16 + Clerk setup that handles users, in-app agents, M2M workers, multi-tenant isolation, and audit. Every snippet is real Clerk TypeScript — not pseudo-code.

### Step 1: Set Up Human User Authentication First

Install `@clerk/nextjs` and wrap the app in `<ClerkProvider>`; `auth()` then verifies the signed-in user on every request.

```ts
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  )
}
```

A minimal route handler confirms the setup:

```ts
// app/api/me/route.ts
import { auth } from '@clerk/nextjs/server'

export async function GET() {
  const { userId, isAuthenticated } = await auth()
  if (!isAuthenticated) return new Response('Unauthorized', { status: 401 })
  return Response.json({ userId })
}
```

### Step 2: Add Session Handling for In-App AI Agents

For an in-session copilot (see [Pattern 1: User-Delegated AI Agents](https://clerk.com/articles/authentication-for-ai-applications.md#pattern-1-user-delegated-ai-agents-human-in-the-loop)), forward the current user's session token into your AI tool. `getToken()` returns a short-lived JWT with 60s TTL and automatic refresh ([Clerk session tokens](https://clerk.com/docs/guides/sessions/session-tokens.md)).

```ts
// app/api/agent/route.ts
import { auth } from '@clerk/nextjs/server'
import { generateText } from 'ai'
import { myToolWithAuth } from '@/lib/tools'

export async function POST(req: Request) {
  const { userId, isAuthenticated, getToken } = await auth()
  if (!isAuthenticated) return new Response('Unauthorized', { status: 401 })

  const sessionToken = await getToken()
  const { prompt } = await req.json()

  const result = await generateText({
    model: 'claude-sonnet-4-6',
    prompt,
    tools: { query: myToolWithAuth({ sessionToken, userId }) },
  })
  return Response.json(result)
}
```

### Step 3: Issue Scoped Tokens for Delegated Agent Actions

Use a Clerk [JWT template](https://clerk.com/docs/guides/sessions/jwt-templates.md) to mint a downstream token carrying agent context. Define the template in the Dashboard's [JWT templates](https://dashboard.clerk.com/~/jwt-templates) section (or via BAPI):

```json
{
  "aud": "https://example.com",
  "agent_id": "{{user.public_metadata.active_agent_id}}",
  "org_id": "{{org.id}}",
  "org_role": "{{org.role}}",
  "tool_scopes": "{{user.public_metadata.tool_scopes}}"
}
```

Replace `https://example.com` with the URL of your downstream resource server — the `aud` claim binds the token to that audience so the resource can validate it ([RFC 8725 §2.1](https://datatracker.ietf.org/doc/html/rfc8725)).

Request the templated token server-side:

```ts
const { getToken } = await auth()
const agentToken = await getToken({ template: 'agent-context' })
// Pass agentToken as Authorization: Bearer to the downstream service.
```

Auto-included claims (`sub`, `iat`, `exp`, `nbf`) cannot be overridden by the template.

### Step 4: Add Machine-to-Machine Credentials for Autonomous Agents

For a background worker (see [Pattern 2: Autonomous AI Agents](https://clerk.com/articles/authentication-for-ai-applications.md#pattern-2-autonomous-ai-agents-machine-to-machine)), create a Clerk M2M token. Namespace: `clerkClient.m2m` (singular); custom-claims param: `claims` (not `customClaims`); `tokenFormat` defaults to `'opaque'`; `secondsUntilExpiration` defaults to `null` (no expiry).

```ts
// Worker process: mint a JWT M2M token scoped to one agent identity.
import { clerkClient } from '@clerk/nextjs/server'

const client = await clerkClient()
const { token } = await client.m2m.createToken({
  tokenFormat: 'jwt',
  secondsUntilExpiration: 3600,
  claims: { agentId: 'agent-001', workload: 'ticket-classifier' },
})

// Downstream service: verify the M2M token without a network round-trip (JWT only).
// A successful verify() resolves an M2MToken: check its `revoked`/`expired` flags,
// read the machine identity from `subject`, and read custom claims from `claims`.
const result = await client.m2m.verify({ token })
if (!result.revoked && !result.expired) {
  const agentId = result.claims?.agentId // custom claim set at creation
  // Proceed — the token is valid and the agent identity is trusted.
}
```

JWT M2M tokens verify locally (no network) but cannot be revoked post-issuance ([changelog, Mar 2026](https://clerk.com/changelog/2026-02-24-m2m-jwt-tokens.md)). Opaque M2M tokens require a network call per verify but support instant revocation ([token formats](https://clerk.com/docs/guides/development/machine-auth/token-formats.md)) — choose per compromise-recovery SLA. Clerk M2M "scopes" are a communication graph (which machine talks to which), not OAuth capability scopes; Client Credentials is on the Clerk roadmap ([overview](https://clerk.com/docs/guides/development/machine-auth/overview.md)).

### Step 5: Enforce Multi-Tenant Isolation on Every Request

Next.js 16 uses `proxy.ts` for edge routing protection (`middleware.ts` is deprecated — same import, body, and matcher; only the filename changes per [Clerk's middleware reference](https://clerk.com/docs/references/nextjs/clerk-middleware.md)). The per-request token check runs inside each route handler via `auth({ acceptsToken: [...] })`.

```ts
// proxy.ts (Next.js 16 — replaces middleware.ts)
import { clerkMiddleware } from '@clerk/nextjs/server'

export default clerkMiddleware()

export const config = {
  matcher: ['/((?!_next|[^?]*\\.(?:html?|css|js|png|svg|woff2?)).*)', '/(api|trpc)(.*)'],
}
```

The handler accepts session / OAuth / M2M / API-key tokens and runs the tenant check with a 403 `application/problem+json` response on mismatch. `acceptsToken` is valid on `auth()` and `authenticateRequest()` — not on `clerkMiddleware()`. Prefer the explicit array over `'any'`.

```ts
// app/api/contacts/[id]/route.ts
import { auth } from '@clerk/nextjs/server'
import { getContactOrg } from '@/lib/contacts'

export async function GET(_req: Request, { params }: { params: { id: string } }) {
  const a = await auth({
    acceptsToken: ['session_token', 'oauth_token', 'm2m_token', 'api_key'],
  })
  if (!a.isAuthenticated) return problem(401, 'invalid_token')

  const resourceOrg = await getContactOrg(params.id)
  const orgId = a.tokenType === 'session_token' ? a.orgId : a.claims?.org_id
  if (!orgId || orgId !== resourceOrg) return problem(403, 'invalid_request', 'Tenant mismatch')

  return Response.json({ id: params.id })
}

function problem(status: number, error: string, detail?: string) {
  return new Response(
    JSON.stringify({ type: `urn:error:${error}`, title: error, status, detail, error }),
    {
      status,
      headers: {
        'Content-Type': 'application/problem+json',
        'WWW-Authenticate': `Bearer error="${error}"`,
      },
    },
  )
}
```

`orgId` is a session-token concept — machine-token branches read it from `claims`. Rely on `tokenType` rather than the presence of `userId` to choose the verification path ([Verifying API Keys](https://clerk.com/docs/guides/development/verifying-api-keys.md), [Verifying OAuth Access Tokens](https://clerk.com/docs/nextjs/guides/development/verifying-oauth-access-tokens.md)).

### Step 6: Add Observability, Audit Trails, and Revocation

Log a structured event per agent action and expose a revocation endpoint. Clerk's M2M revoke signature is `clerkClient.m2m.revokeToken({ m2mTokenId, revocationReason?, machineSecretKey? })` ([docs](https://clerk.com/docs/reference/backend/m2m-tokens/revoke-token.md)) — only opaque M2M tokens can be revoked server-side; JWT M2M tokens rely on TTL + denylist.

```ts
// lib/audit.ts
export type AgentAuditEvent = {
  ts: string
  agent_id: string
  parent_identity: string
  delegation_scope: string[]
  tool_name: string
  tool_params_hash: string
  policy_decision: 'allow' | 'deny'
  trace_id: string
}

export async function logAgentEvent(e: AgentAuditEvent) {
  // Forward to your log sink (Datadog, Elasticsearch, OTel).
  console.log(JSON.stringify({ level: 'info', ...e }))
}

// Revoke an opaque M2M token on user sign-out or detected compromise.
import { clerkClient } from '@clerk/nextjs/server'
const client = await clerkClient()
await client.m2m.revokeToken({
  m2mTokenId: 'm2m_01H...',
  revocationReason: 'user_revoked',
})
```

## Choosing an Authentication Provider for AI SaaS Applications

### Build vs Buy for AI Agent Authentication

OAuth 2.1 + PKCE + JWT validation + DCR + CIMD + token vaults + FGA hooks + audit is a very deep stack. **89% of AI-powered APIs rely on insecure authentication** ([Wallarm, 2025](https://www.wallarm.com/press-releases/wallarm-releases-2025-api-threatstats-report)) and **48% of cybersecurity professionals identify agentic AI as the single most dangerous attack vector** ([Bessemer, 2026](https://www.bvp.com/atlas/securing-ai-agents-the-defining-cybersecurity-challenge-of-2026)). Buy unless you have a strong reason not to.

### Evaluation Criteria for AI-Era Auth

Six criteria worth checking against any shortlist:

1. **Non-human identity support** — first-class M2M credentials with custom claims and revocation, not "use a service account" ([WorkOS criteria](https://workos.com/blog/best-oauth-oidc-providers-for-authenticating-ai-agents-2025)).
2. **Granular token scoping** — scopes + claims + clean hand-off to an FGA engine. Progressive and tenant-scoped scopes signal maturity ([Descope](https://www.descope.com/blog/post/progressive-scoping)).
3. **Multi-tenant primitives** — Organizations, roles, per-org JWT claims, RBAC without SQL hand-rolling.
4. **MCP-compatible flows** — OAuth 2.1 + PKCE, PRM + AS metadata, DCR **or** CIMD.
5. **OAuth provider capability** — your app as an OAuth IdP so external agents authenticate via user consent ([Clerk as OAuth IdP](https://clerk.com/docs/advanced-usage/clerk-idp.md), [Stytch Connected Apps](https://stytch.com/docs/guides/connected-apps/ai-agents), [Descope Inbound Apps](https://www.descope.com/press-release/agentic-identity-hub-2.0)).
6. **Audit logging and session management** — structured events, session introspection API, session lifecycle webhooks, compatibility with [OpenTelemetry GenAI](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-agent-spans/).

### Why Clerk Fits Modern AI Applications

Clerk ships five AI-specific capabilities:

- **M2M tokens** — Public Beta Aug 2025, GA Oct 14 2025, JWT format Mar 5 2026 ([changelog](https://clerk.com/changelog/2026-02-24-m2m-jwt-tokens.md)). Custom `claims`; opaque (instant revocation) or JWT (local verification). Revoke via `clerkClient.m2m.revokeToken({ m2mTokenId })`.
- **Organizations** — org-scoped identity, up to 10 [custom roles](https://clerk.com/glossary/custom-roles.md) per application instance, BAPI CRUD over roles and permissions, org claims on every session JWT ([changelog](https://clerk.com/changelog/2025-11-24-organization-roles-and-permission-bapi-management.md)).
- **OAuth Provider** — Authorization Code + PKCE, DCR (RFC 7591) as a Dashboard toggle, JWT OAuth access tokens for networkless verification ([changelog](https://clerk.com/changelog/2026-01-08-jwt-oauth-access-tokens.md)).
- **Short-TTL sessions** — 60-second default with proactive refresh in Core 3 ([changelog](https://clerk.com/changelog/2026-03-03-core-3.md)); JWT templates for agent context.
- **MCP primitives** — Next.js: `verifyClerkToken` from `@clerk/mcp-tools/next` plugs into Vercel's `withMcpAuth` wrapper. Express: `mcpAuthClerk` middleware collapses the pattern into one line. Clerk also hosts a docs MCP server at `https://mcp.clerk.com/mcp` for AI coding assistants ([Clerk MCP server guide](https://clerk.com/docs/guides/ai/mcp/clerk-mcp-server.md), [changelog](https://clerk.com/changelog/2026-01-20-clerk-mcp-server.md)) — a developer tool, not an auth proxy.

### Getting Started With Clerk for AI Agent Authentication

A 4-step path:

1. Install `@clerk/nextjs` (Next.js 16) or `@clerk/express`. Optionally scaffold via the Clerk CLI (`npm install -g clerk`, then `clerk init`) — [shipped 2026-04-22](https://clerk.com/changelog/2026-04-22-clerk-cli.md) with three commands: `clerk init` (detects your framework and scaffolds Clerk into the project), `clerk config` (manages application settings from the command line), and `clerk api` (interacts with the Backend API). A `clerk deploy` command is in development.
2. Enable machine auth in the Dashboard — [M2M authentication](https://dashboard.clerk.com/~/machines), [API keys](https://dashboard.clerk.com/~/api-keys), and [OAuth applications → Settings](https://dashboard.clerk.com/~/oauth-applications?oauth_applications_tab=settings) (DCR toggle).
3. Define a JWT template for agent context (see [Step 3: Issue Scoped Tokens for Delegated Agent Actions](#step-3-issue-scoped-tokens-for-delegated-agent-actions)).
4. In route handlers, call `auth({ acceptsToken: ['session_token', 'oauth_token', 'm2m_token', 'api_key'] })` to accept every token type on the same endpoint.

The manual quickstart (wrap `app/layout.tsx` in `<ClerkProvider>`, create `proxy.ts` with `clerkMiddleware()`) remains supported.

## Quick Reference Checklists

Three copy-pastable checklists for AI agent auth. Each item is independently verifiable.

### Token Scoping Checklist

- [ ] Access token TTL ≤ 1 hour (≤ 15 mins for high-risk actions).
- [ ] Refresh tokens rotate on every use with reuse detection.
- [ ] Tokens are sender-constrained where the client supports it.
- [ ] Scopes are minimum-required; never `*` / `all`.
- [ ] Action-specific permissions enforced server-side.
- [ ] `aud` claim validated on every request.
- [ ] `exp`, `iat`, `nbf` verified against server clock.
- [ ] Tokens include `agent_id` and `parent_identity` claims.

### Multi-Tenant Agent Checklist

- [ ] Every agent token carries `org_id` (or tenant equivalent).
- [ ] Middleware rejects cross-tenant access with 403 + audit log.
- [ ] DB policies enforce `tenant_id` at row level (RLS or equivalent).
- [ ] Agents never hold long-lived credentials spanning tenants.
- [ ] Agent memory / context is isolated per tenant (no shared caches).
- [ ] Separate encryption keys per tenant for sensitive data.
- [ ] Revocation cascades to all per-tenant tokens on tenancy changes.

### Security Review Checklist

- [ ] No long-lived static secrets in agent code or config.
- [ ] MCP servers on Streamable HTTP, never stdio in production (April 2026 CVE cluster).
- [ ] Token vault for third-party OAuth credentials (agent never sees refresh tokens).
- [ ] Per-agent identity (no shared credentials across agents).
- [ ] Structured audit logs with tool invocation hashes.
- [ ] Human-in-the-loop (CIBA) required for high-risk actions.
- [ ] Rate limits on both RPM and token-per-minute.
- [ ] Incident response plan includes token revocation at scale.

## Conclusion

Authenticating AI agents requires shifting from human-centric, long-lived sessions to scoped, highly ephemeral credentials. By treating every non-human actor as a first-class identity and enforcing multi-tenant isolation at both the application and database layers, you can safely deploy user-delegated copilots and autonomous background workers without compromising user data. As standards like OAuth 2.1, RFC 8693, and MCP consolidate, building on an identity provider that natively understands machine-to-machine patterns and context propagation is critical to operating AI SaaS at scale.

## In this series

1. [Authentication for AI Applications](https://clerk.com/articles/authentication-for-ai-applications.md)
2. **Authentication for AI Applications - Part 2** (you are here)
