Integrate

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.

Agent verified flow
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

  1. Enable agents to exchange their session for an audience-specific ID-JAG on user consent.
  2. Host JWKS (and optionally a CIMD) so downstream services can verify the assertions you sign.
  3. Direct agents to introspect the agent_auth block of the consuming service's .well-known/oauth-authorization-server.
  4. 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:

  1. 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 a WWW-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"]
    }
  2. Authorization Server metadata at <authorization_servers[0]>/.well-known/oauth-authorization-server — this is where the agent_auth block lives. The agent reads authorization_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:

json
{
  "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.

json
{
  "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:

http
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.

json
{
  "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"]
}
json
{
  "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:

json
{ "error": "invalid_audience", "message": "..." }
Error codeMeaning
invalid_issuerToken iss isn't in the service's trusted providers list.
invalid_signatureJWKS lookup failed or the signature didn't verify against any known key.
expiredexp is in the past.
replay_detectedjti has already been seen within the replay window.
invalid_audienceaud doesn't match the service's auth server.
invalid_client_idclient_id doesn't resolve to a known provider identity.
missing_verified_emailNeither email_verified nor phone_number_verified is true.
unsupported_credential_typeRequested credential type isn't offered by the service.
insufficient_user_authenticationAuth 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:

http
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.