developers

Securing AI Agents with Auth0 CIMD and Token Exchange

AI agents break static authorization. Learn how Auth0 Strict Mode, CIMD, and Token Exchange keep a permission ceiling and a verifiable user-to-agent delegation chain.

In Part One of this series, we worked through three foundational authorization scenarios. In Scenario two, we introduced the third-party partner app: a separate company's application utilizing a DCR registered Client. To secure it, we established an immutable permission ceiling using an Auth0 Client Grant.

The formula was straightforward for the analytics partner, that resolved to read:analytics and nothing more, regardless of what the app requested:

Scopes in Scenario 2 tokens

That ceiling is the property we want to keep.

But what happens when that "partner app" is not a human-facing web application, but an autonomous AI agent?

Three Assumptions That Break With Agents

Classic App - 1 Hop vs AI Agent - Multi Hop

Our standard integration model relied on a predictable world, assuming three things:

  1. Static Permissions: You pre-registered the application and assigned its Client Grant manually in the Dashboard.
  2. Predictable Life Cycles: Scopes were static — known at registration time and unchanged at runtime.
  3. Direct Delegation: The app made direct API calls, acting as a single hop from the client to the resource server.

AI agents shatter all three assumptions.

Pre-registration does not scale when an agentic platform might spin up thousands of ephemeral agent instances, each needing a distinct OAuth client identity. Scopes are discovered dynamically at runtime based on the agent's reasoning. And most importantly, agents are multi-hop by nature: a user talks to a control server, which calls a downstream API, which triggers a data warehouse.

At every hop, the access token needs to carry two critical pieces of context: which user authorized this, and which agent is acting on their behalf. Let's look at how we can rebuild these architectural pillars using modern Auth0 capabilities.

The New Baseline for Third-Party Clients is Strict Mode

Before strict mode, a third-party app could theoretically access an API without a Client Grant if the API's underlying access policy was permissive. Strict mode closes that gap permanently.

When you enable Strict Mode in an API, Auth0 enforces the following: :

  • PKCE is mandatory: Strict alignment with OAuth 2.1 best practices.
  • Explicit Grants Required: Every third-party client must have an explicit Client Grant to access an API in your tenant
  • No Workarounds: The permission ceiling is enforced, ignoring permissive local policies.

Configure it in the Auth0 Dashboard:

Applications → APIs → [Your API] → Settings → Third-party Security Mode → Strict

Or via the Management API:

PATCH /api/v2/resource-servers/{id}
{
  "third_party_security_mode": "strict"
}

For agents, this matters precisely because agent registrations happen programmatically and at scale. Without strict mode, a misconfigured Client ID Metadata Documents (CIMD) registration could produce a rogue client. Strict Mode makes that structurally impossible.

Flow Compatibility Note: While Strict Mode prevents dynamically registered clients from obtaining machine-to-machine (Client Credentials) grants to prevent them from acting autonomously as superusers, it fully supports user-centric delegation. If your AI agent executes tasks with an active user in the loop, DCR-registered clients can safely use the Authorization Code Flow with PKCE to obtain user-delegated tokens.

Bootstrapping Identity with CIMD

Classic Dynamic Client Registration is powerful but inherently risky without guardrails. CIMD is the constrained, secure alternative.

CIMD Flow

Instead of an agent sending its desired configuration directly to Auth0, the agent publishes a metadata document at a well-known HTTPS URL. Auth0 fetches, validates, and locks in that document before issuing a client_id.

The Metadata Document (hosted by the agent):

{
  "client_name": "Analytics Reporting Agent v1.2",
  "redirect_uris": ["https://agent.example.com/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_method": "private_key_jwt",
  "scope": "read:analytics"
}
Infrastructure Prerequisite: The externalclientid you register with Auth0 must resolve to a publicly accessible, stable HTTPS endpoint hosted somewhere. Auth0’s authorization server fetches this document out-of-band to verify the agent's configuration. If this endpoint goes offline, authorization requests will fail.

Currently Auth0 supports pre-registering Third Party Client using the Management API or the Dashboard. In a future release support for dynamic registered clients through CIMD will follow.

Using the Management API the registration request will be:

POST https://<YOUR_TENANTS_MANAGEMENTAPI_DOMAIN>/api/v2/clients/cimd/register
Content-Type: application/json

{
  "external_client_id": "https://<AGENT_URL>/.well-known/client-metadata.json"
}

Auth0's response:

{
    "client_id": "tpc_8AvZNHu2qSK5KxoSMVAv7c",
    "mapped_fields": {
        "external_client_id": "https://<AGENT_URL>/.well-known/client-metadata.json",
        "name": "Analytics Reporting Agent v1.2",
        "callbacks": [
            "https://agent.example.com/callback"
        ],
        "grant_types": [
            "authorization_code",
            "refresh_token"
        ],
        "app_type": "native",
        "token_endpoint_auth_method": "private_key_jwt"
    }
}

Auth0's response returns a client_id. Because this is a third-party client, the API's configured default permissions apply automatically at registration, instantly creating our vital permission ceiling.

The Delegation Chain

CIMD successfully gives the agent a client identity. But when the agent calls a downstream service, the service needs cryptographic proof of the delegation chain. This is where RFC 8693 Token Exchange comes in.

Here is the flow:

  1. The user authenticates and receives a standard subject token.
  2. The agent needs to call the downstream API. It presents the user's token, along with its own client credentials, to Auth0’s /oauth/token endpoint..
  3. Auth0 issues a delegated token that carries both the original user subject (sub) and the acting agent context (act).

The token exchange request:

POST https://YOUR_DOMAIN/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=eyJhbG...
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&actor_token=eyJhbG...
&actor_token_type=urn:ietf:params:oauth:token-type:jwt
&audience=https://reporting.example.com/api
&scope=read:analytics
Note: In production environments, prefer using privatekeyjwt or mTLS over a static client_secret for maximum security.

The resulting JWT (Decoded Payload):

{
  "iss": "https://YOUR_DOMAIN/",
  "sub": "auth0|user_abc123",
  "aud": "https://reporting.example.com/api",
  "scope": "read:analytics",
  "act": {
    "sub": "tpc_AgxY8z..."
  },
  "exp": 1748390400
}

Notice the payload. The sub (subject) is still the user. But we now have an act (actor) claim carrying the agent's client ID. Every downstream service receiving this token can now cryptographically verify exactly who is acting, and on whose behalf.

The Agentic Workflow

Let's replay Scenario Two from Part One in the series, but now with our new agentic architecture:.

  1. Bootstrap (CIMD): The agent publishes its metadata. Auth0 validates it, returns a tpc_ client ID, and automatically applies the read:analytics permission ceiling.
  2. Authorize (User Consent): The user interacts with the agent, sees a consent screen, and approves. The agent receives an access token scoped to read:analytics, bound to the user.
  3. Execute (Token Exchange): The agent needs to pull data from a deep downstream reporting service. It exchanges the user's token via RFC 8693, resulting in a token with an act claim proving its delegation rights.

The authorization formula from Part One has successfully stretched across the multi-hop chain: (User Permissions ∩ App Permissions) ∩ Requested Scopes, strictly enforced at every link.

What Strict Mode Does Not Cover

Client Grants control what an agent type can do (read:analytics). But they do not control which specific records it can access. What if report access should be strictly scoped so the agent can only read reports that this specific user owns?

That requires Fine-Grained Authorization (FGA) — relationship-based access control where the decision depends on the relationship between a user, a resource, and a context. Auth0 FGA handles exactly this, and we will cover it in Part Three.

Put It Into Practice

  • Enable strict mode on your API.
  • Configure default permissions.
  • Publish a CIMD document: Host it at a stable HTTPS URL, then register a Client with your metadata to test agent registration.
  • Try Token Exchange and verify the result using jwt.io.

Resources

If you do not already have an Auth0 account yet, create one for free.