For agent providers
Trusted agent providers — companies whose agents act on behalf of users (OpenAI, Anthropic, Cursor, and similar) — power the agent verified flow by asserting a user's identity to a downstream service. The artifact is an Identity Assertion JWT Authorization Grant (ID-JAG). Signing ID-JAGs makes your surface the identity broker for every service a user's agent touches — you keep the consent prompt, the revocation UX, and the delegation audit trail inside your product instead of leaking them to wherever the user would otherwise paste an API key.
auth.md also defines a user claimed flow, which is between an app and its user via OTP and does not require agent provider participation. This guide covers only the agent verified flow.
Agent verified flow sequence
The agent discovers the service's auth posture via its Protected Resource
Metadata (PRM), asks the user for consent, requests an audience-specific
ID-JAG from you, and exchanges it at the service's /agent/auth endpoint
for credentials.
sequenceDiagram
actor User
participant Agent
participant Provider as Agent Provider
participant Service
Agent->>Service: GET /api/resource
Service-->>Agent: 401 Unauthorized<br/>WWW-Authenticate: Bearer resource_metadata="..."
Agent->>Service: GET /.well-known/oauth-protected-resource
Service-->>Agent: 200 OK (PRM with authorization_servers)
Agent->>Service: GET /.well-known/oauth-authorization-server
Service-->>Agent: 200 OK (AS metadata with agent_auth block)
Agent->>User: Consent to assert identity to audience?
User-->>Agent: Consent granted
Agent->>Provider: Request audience-specific ID-JAG
Provider-->>Agent: 200 OK (ID-JAG)
Agent->>Service: POST /agent/auth<br/>{ type: identity_assertion, assertion: ID-JAG }
Service->>Provider: GET /.well-known/jwks.json
Provider-->>Service: 200 OK (JSON Web Key Set)
Service->>Service: Verify signature + claims, match user
Service-->>Agent: 200 OK (credentials)Minimum implementation
- Enable agents to exchange their session for an audience-specific ID-JAG on user consent.
- Host JWKS (and optionally a CIMD) so downstream services can verify the assertions you sign.
- Direct agents to introspect the
agent_authblock of the consuming service's.well-known/oauth-authorization-server. - Accept revocation calls from the user's control plane and POST a logout token to the service's revocation endpoint.
Discovering agent auth
Agents reach the agent registration pathway through service documentation,
SDKs, self-documenting APIs, and an
RFC 9728 (OAuth 2.0 Protected Resource Metadata)
enrichment layer. Services may also publish an auth.md document
containing a breadcrumb to the protected resource document.
Discovery is two-hop:
-
Protected Resource Metadata (PRM) at
.well-known/oauth-protected-resource(per RFC 9728) — the resource server advertises its authorization servers. On any 401, the resource server includes aWWW-Authenticate: Bearer resource_metadata="..."header pointing here:json { "resource": "https://api.service.com/", "resource_name": "Service", "resource_logo_uri": "https://service.com/logo.png", "authorization_servers": ["https://auth.service.com/"], "scopes_supported": ["api.read", "api.write"], "bearer_methods_supported": ["header"] } -
Authorization Server metadata at
<authorization_servers[0]>/.well-known/oauth-authorization-server— this is where theagent_authblock lives. The agent readsauthorization_servers[0]from the PRM and fetches:json { "resource": "https://api.service.com/", "authorization_servers": ["https://auth.service.com/"], "scopes_supported": ["api.read", "api.write"], "bearer_methods_supported": ["header"], "agent_auth": { "skill": "https://workos.com/auth.md", "register_uri": "https://auth.service.com/agent/auth", "claim_uri": "https://auth.service.com/agent/auth/claim", "revocation_uri": "https://auth.service.com/agent/auth/revoke", "identity_types_supported": ["anonymous", "identity_assertion"], "anonymous": { "credential_types_supported": ["api_key"] }, "identity_assertion": { "assertion_types_supported": [ "urn:ietf:params:oauth:token-type:id-jag", "verified_email" ], "credential_types_supported": ["access_token", "api_key"] }, "events_supported": [ "https://schemas.workos.com/events/agent/auth/identity/assertion/revoked" ] } }
Minting the ID-JAG
On consent, mint a short-lived JWT signed with a key published in your JWKS. The header carries the ID-JAG content type marker; the payload carries the audience-scoped claims:
{
"typ": "oauth-id-jag+jwt",
"alg": "ES256", // or RS256, etc.
"kid": "<provider key id>"
}
.
{
// required
"iss": "https://api.agent-provider.com",
"sub": "<opaque user identifier>",
"aud": "https://auth.service.com",
"client_id": "<iss or CIMD URL>",
"jti": "<unique identifier for the token to prevent replay>",
"iat": <issuance epoch seconds>,
"exp": <iat + 5m>,
"email": "user@example.com",
"email_verified": true,
// optional
"amr": ["mfa"],
"auth_time": <original auth epoch seconds>,
"name": "Jane Smith",
"phone_number": "+15553805188",
"phone_number_verified": false,
"resource": "https://api.service.com",
// optional agent metadata
"agent_platform": "<your-agent-surface>",
"agent_context_id": "<chat-id>"
}At minimum, the consumer needs iss, sub, aud, client_id, jti,
iat, exp, and at least one verified contact (email_verified or
phone_number_verified) to match or provision a user.
Hosted discovery documents
Publish your
JSON Web Key Set (JWKS)
— conventionally at .well-known/jwks.json — so consuming services can
verify your ID-JAG signatures.
Optional: Client ID Metadata Document (CIMD)
Host an
OAuth Client ID Metadata Document
and use its URL as the client_id value in the ID-JAG. This decouples
your provider identity from your signing keys — you can rotate JWKS
without churning every consumer's trust list — and makes it convenient
for trusted agent registries to list providers. Adopt this if you expect
signing-key rotation or registry listing to matter; otherwise the
client_id can be your issuer URL.
{
"client_id": "https://api.agent-provider.com/agent-auth.json",
"client_name": "Agent Provider",
"logo_uri": "https://agent-provider.com/logo.png",
"client_uri": "https://agent-provider.com",
"tos_uri": "https://agent-provider.com/tos",
"policy_uri": "https://agent-provider.com/privacy",
"token_endpoint_auth_method": "private_key_jwt",
"jwks_uri": "https://agent-provider.com/.well-known/jwks.json",
"scope": "openid email profile"
}Acquiring credentials
Once the ID-JAG is minted, the agent exchanges it at the service's
/agent/auth endpoint:
POST /agent/auth HTTP/1.1
Host: auth.service.com
Content-Type: application/json
{
"type": "identity_assertion",
"assertion_type": "urn:ietf:params:oauth:token-type:id-jag",
"assertion": "eyJhbGc...",
"requested_credential_type": "access_token"
}The spec supports both access_token and api_key credentials, with the
choice up to the service.
{
"registration_id": "reg_...",
"registration_type": "agent-provider",
"credential_type": "access_token",
"credential": "<token>",
"credential_expires": "2026-05-04T13:00:00.000Z",
"scopes": ["api.read", "api.write"]
}{
"registration_id": "reg_...",
"registration_type": "agent-provider",
"credential_type": "api_key",
"credential": "sk_live_...",
"credential_expires": null,
"scopes": ["api.read", "api.write"]
}Access tokens issued from ID-JAG verification do not include a refresh token — to extend access, the agent presents a fresh ID-JAG.
Errors
Consumers return errors with the codes below in a JSON body:
{ "error": "invalid_audience", "message": "..." }| Error code | Meaning |
|---|---|
invalid_issuer | Token iss isn't in the service's trusted providers list. |
invalid_signature | JWKS lookup failed or the signature didn't verify against any known key. |
expired | exp is in the past. |
replay_detected | jti has already been seen within the replay window. |
invalid_audience | aud doesn't match the service's auth server. |
invalid_client_id | client_id doesn't resolve to a known provider identity. |
missing_verified_email | Neither email_verified nor phone_number_verified is true. |
unsupported_credential_type | Requested credential type isn't offered by the service. |
insufficient_user_authentication | Auth context didn't meet policy (RFC 9470 pattern). |
Downstream verification
Services maintain a list of trusted agent providers. They'll attempt to
match an existing customer by (iss, sub) first, then by verified
email/phone for JIT provisioning, and decide whether to create a new
account or issue credentials for an existing one.
Services reject ID-JAGs with neither a verified email nor a verified phone number — there's no basis for matching and no channel for user-facing communications (revocation notices, claim emails). If the identity assertion validates, the service returns credentials of the requested type.
Tracking and revocation
Track the services to which identity assertions have been delegated so the user can revoke credentials from a control plane in your product. The discovery document and the registration response both carry the revocation endpoint. The mechanism is a logout token posted to that endpoint:
POST /agent/auth/revoke HTTP/1.1
Host: auth.service.com
Content-Type: application/logout+jwt
{
"typ": "logout+jwt",
"alg": "ES256", // or RS256, etc.
"kid": "<provider key id>"
}
.
{
"iss": "https://api.agent-provider.com",
"sub": "<opaque user identifier>",
"aud": "https://auth.service.com",
"jti": "<unique identifier to prevent replay>",
"iat": <epoch seconds>,
"events": {
"https://schemas.workos.com/events/agent/auth/identity/assertion/revoked": {}
}
}Receiving services invalidate the credentials associated with the ID-JAG. Expect this surface to extend with SET / CAEP / RISC event communication for session changes beyond revocation, delivered via webhook or SSE.