+
+
+
+
+
+
+
+
Blog/Guides

OIDC for AI Agents: The Complete Guide

BPBinoy Perera

OAuth and OpenID Connect for AI agents, with the agent as the subject. The live AgentID discovery document, the code flow with PKCE S256, ES256 tokens, the two client tiers, every scope and claim, where owner_email lives, browser enrollment, and the threat model.

Guide
Guides
AgentID
oidc
oauth
pkce
TL;DR
  • OIDC gives you identity; OAuth gives you permission. Agents need both, and the delegation products compared below only do the second. An OIDC provider for agents issues an id_token that says which agent this is.
  • The agent is the principal. In AgentID the agent has its own account, inbox, and credential. The human owner is a claim you can request, not the account you are logging in.
  • Everything is standard. Authorization code, PKCE S256, ES256 signatures, discovery, JWKS, RFC 7591 dynamic registration. Any OIDC library or auth platform works.
  • Owner claims live at /userinfo. owner_email and owner_name are never in the id_token. Registered clients read them with the access token.

OpenID Connect for AI agents means using the same identity protocol your login already speaks, with the agent as the subject instead of a person.

The agent holds a signing key. An OIDC provider built for agents issues an id_token that names the agent: a stable sub and its verified email. Your application verifies that token against published keys the way it verifies a Google sign-in.

OAuth alone cannot do this. OAuth answers what a client may do on a user's behalf, not who the client is.

AgentID, the sign-in for AI agents from AgentMail, is an OIDC provider that does it for the open web:

  • Flow: authorization code with PKCE.
  • Tokens: ES256 signatures.
  • Discovery: https://auth.agentid.com/.well-known/openid-configuration.
  • Registration: RFC 7591, for clients that need the owner's identity.
  • Enrollment: a one-time browser enrollment on the agent's side, so no human has to be present at sign-in.

This guide covers the whole protocol for developers on the relying-party side, with the exact values the live issuer publishes.

On this page

Why is OAuth alone not enough to verify an AI agent?

OAuth 2.0 is an authorization framework, so on its own it cannot verify an AI agent. Its output is an access token that lets a client call a resource on behalf of a resource owner, scoped to what that owner granted.

The token says nothing reliable about who the client is. client_id is self-asserted for public clients, and there is no standard claim for "this is the agent, and this is the human behind it."

Every agent auth product built on OAuth alone inherits this. It can tell you a user delegated authority to something. It cannot tell you which something, or vouch for it to a party who has never met it.

OpenID Connect adds the identity layer. An OIDC provider authenticates a subject and issues an id_token: a signed JWT with iss, sub, aud, exp, iat, and whatever profile claims were granted.

The relying party verifies the signature against the provider's JWKS and trusts the claims because it trusts the issuer. Sign in with Google is OIDC. So is Sign in with AgentID; the only difference is what kind of entity the subject is.

For agents, that difference matters:

  • A delegate-model product issues tokens about a human, with the agent as an unnamed bearer.
  • An agent-as-principal provider issues tokens about the agent, with the human as an optional claim.

The second is the one that lets a third-party app say "this is agent X, owned by person Y, and I have seen X before."

The vocabulary for this split is covered in agent owner verification vs end-user authentication.

What does the discovery document say?

AgentID publishes its OIDC discovery document at the standard path. Every value below is taken from the live document as of September 14, 2026.

FieldValueWhat it means for you
issuerhttps://auth.agentid.comPaste this into any "issuer" or "well-known" field; the client derives the rest.
authorization_endpoint/v0/authorizeWhere you redirect the agent.
token_endpoint/v0/tokenWhere your callback exchanges the code.
userinfo_endpoint/v0/userinfoThe only place owner claims are served.
jwks_uri/v0/jwks.jsonPublic keys for verifying id_tokens.
registration_endpoint/v0/registerRFC 7591 dynamic registration, for the registered tier.
grant_types_supportedauthorization_codeNo refresh tokens, no client credentials, no implicit flow.
response_types_supportedcodeCode flow only.
code_challenge_methods_supportedS256PKCE with SHA-256. The plain method is not accepted.
id_token_signing_alg_values_supportedES256ECDSA P-256. A client that insists on RS256 will fail.
token_endpoint_auth_methods_supportedclient_secret_basic, client_secret_post, noneOpen clients use none; registered clients use a secret.
scopes_supportedopenid, email, profile, owner_profile, owner_emailNo offline_access, because there are no refresh tokens.
authorization_response_iss_parameter_supportedtrueThe callback carries iss (RFC 9207), so mix-up attacks are detectable.

Two absences are deliberate and will surprise a generic connector: there is no end_session_endpoint, and offline_access is not offered.

Sessions are your application's. AgentID authenticates the agent and gets out of the way.

How does the OIDC sign-in flow work for AI agents, step by step?

The relying-party side of an AgentID sign-in is the textbook authorization code flow with PKCE, in five steps.

Step 1: redirect the agent. Your app generates a PKCE verifier and challenge, plus state, and redirects the agent:

GET https://auth.agentid.com/v0/authorize
  ?response_type=code
  &client_id=https://yourapp.com
  &redirect_uri=https://yourapp.com/callback
  &scope=openid%20email
  &state=<opaque>
  &code_challenge=<base64url(sha256(verifier))>
  &code_challenge_method=S256

Step 2: the agent's browser signs in. The agent's enrolled browser completes the sign-in. A browser that already holds an AgentID session continues on its own; otherwise the page shows the agent one command that creates one.

A disclosure screen shows the requesting app and what it will receive. There is no password form because there is no password.

Step 3: AgentID redirects back. AgentID redirects to your redirect_uri with code, state, and iss.

Step 4: exchange the code. Your callback exchanges the code:

POST https://auth.agentid.com/v0/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=<code>
&redirect_uri=https://yourapp.com/callback
&client_id=https://yourapp.com
&code_verifier=<verifier>

The response carries an id_token and an access_token. Codes are single-use, expire in seconds, and replay is rejected.

Step 5: verify and look up the account. Verify the id_token (see below), create or look up the agent account by sub, and, if you hold an owner scope, call /v0/userinfo with the access token.

Nothing here is AgentID-specific in shape. A library that implements OIDC Core 1.0 and RFC 7636 handles it unchanged.

The per-platform dashboards do it for you:

What are the two client tiers?

AgentID has two client tiers: an open tier that requires no registration, and a registered tier for clients that need more.

An open client works like this:

  • It identifies by origin. Its client_id is an https URL the client controls, such as https://yourapp.com, and its redirect URI sits on that same origin.
  • It has no secret. PKCE is mandatory, and the origin is the whole trust anchor.
  • It gets two scopes. Open clients may request openid and email.
  • It cannot run on localhost. Because the anchor is an https origin, an open client cannot run on http://localhost; local development starts at registration.

A registered client is created with one call to /v0/register, authenticated with the developer's own AgentMail API key as the RFC 7591 initial access token:

POST https://auth.agentid.com/v0/register
Authorization: Bearer <your AgentMail API key>
Content-Type: application/json

{
  "client_name": "My Tool",
  "redirect_uris": ["https://yourapp.com/callback"],
  "token_endpoint_auth_method": "client_secret_basic"
}

The response includes an opaque client_id and a client_secret that is shown once. Registered clients get:

  • Up to 10 redirect URIs.
  • Loopback and http redirects for development.
  • Confidential token exchange.
  • Per-client sign-in history.
  • Access to the profile, owner_profile, and owner_email scopes.

Registration-time scope metadata is ignored: the scopes you actually receive are the ones you send to /authorize.

Registered clients may use either S256 PKCE or an OIDC nonce; open clients must use PKCE.

Registration is gated on an AgentMail API key on purpose. Every registered client is an AgentMail account, which is how a free product pays for itself.

Which scopes and claims exist?

AgentID offers five scopes: openid, email, profile, owner_profile, and owner_email. Scopes are space-delimited at /authorize, and a request with no scope falls back to openid email.

ScopeClaims it unlocksDelivered inTier
openidsub (plus iss, aud, exp, iat, jti, nonce)id_tokenOpen and registered
emailemail, email_verifiedid_token and /userinfoOpen and registered
profilename (the agent's display name)id_token and /userinfoRegistered
owner_profileowner_name/userinfo onlyRegistered
owner_emailowner_email/userinfo onlyRegistered

Registered clients also receive a scope claim in the id_token listing what was granted.

Scope-gated claims are omitted rather than nulled when not granted, so test for presence. A decoded id_token for a registered client looks like this:

{
  "iss": "https://auth.agentid.com",
  "aud": "b7d41e0a-2c65-4f8b-9d31-0a5e7c2f4b18",
  "sub": "a91f2c8e...43",
  "scope": "openid email profile owner_email",
  "email": "support@acme.agentmail.to",
  "email_verified": true,
  "name": "Acme Support",
  "iat": 1767225000,
  "exp": 1767225600,
  "jti": "9f1c2a44-3e77-4c19-9a2e-6b0d5f8e1c33"
}

Note that owner_email is in the scope claim and absent from the token body. That is by design.

Where does owner_email actually live?

The owner_email claim lives at /v0/userinfo, and nowhere else.

When /token runs, AgentID writes the owner snapshot to the grant record. /userinfo dereferences it for a Bearer access token and re-verifies the live inbox on every call.

The id_token signing code takes no owner input at all, so no id_token can carry the claim.

GET https://auth.agentid.com/v0/userinfo
Authorization: Bearer <access_token>

{
  "sub": "a91f2c8e...43",
  "email": "support@acme.agentmail.to",
  "email_verified": true,
  "name": "Acme Support",
  "owner_name": "Maya Chen",
  "owner_email": "maya@acme.com"
}

The reason is leakage. id_tokens end up in browser storage, request logs, and session cookies.

The owner's address is personal data about someone who is not the subject of the token. Holding it on the grant keeps it out of every one of those places.

Owner disclosure is also enforced:

  • The credential must permit it. The agent's signing credential must carry permission to disclose its owner.
  • The organization needs one owner. The organization behind the agent must have exactly one designated owner.
  • A refused disclosure fails the sign-in. When a client requests owner_email and the credential does not permit it, the sign-in fails with a 403 rather than returning a token silently missing the claim.

A relying party registered for owner_email can therefore rely on it being present in /userinfo whenever a sign-in succeeds.

Auth platforms build the session user from the id_token. On Supabase, Clerk, or Better Auth the owner fields do not appear on the session object; fetch them from /userinfo in your callback and store them yourself.

The policy side of using them is in how to verify the human behind an AI agent.

How does the agent hold its credential?

The agent holds its credential as a non-extractable private key inside a browser it enrolls once.

  1. The agent posts an AgentMail API key to the enrollment endpoint.
  2. JavaScript on auth.agentid.com generates a P-256 keypair inside the browser.
  3. The browser submits the public key to AgentID.

The private key is a non-extractable WebCrypto key stored in IndexedDB under the auth.agentid.com origin. AgentMail's own wording: "Your private key is generated and stored in your browser and cannot be exported through browser JavaScript APIs."

From then on, each sign-in is a fresh signature over a server-generated transaction.

Nothing reusable crosses the wire. The API key was used once at enrollment and is not part of any sign-in, and the app never sees the key or a bearer credential it could replay.

Headless browsers work. An agent that has no browser at all does not lose the ability to sign in; its owner completes the sign-in in the AgentMail console, in the same browser the flow started in.

Revocation is per credential. Revoke an enrolled browser and that agent stops signing in from it; other agents, and the owner's own accounts, are untouched.

The step-by-step account creation from the agent's point of view is in how an AI agent creates its own account.

How do you verify and manage OAuth tokens for AI agents?

Verify an AgentID id_token the same way you verify any OIDC id_token: check its ES256 signature against the published JWKS, then check the claims.

  1. Fetch the JWKS from https://auth.agentid.com/v0/jwks.json.
  2. Select the key by kid.
  3. Verify the ES256 signature.
  4. Check that iss equals https://auth.agentid.com.
  5. Check that aud equals your exact client_id.
  6. Check that exp is in the future.
  7. If you sent a nonce, check that it is echoed back.

On the issuer side, AgentID:

  • Holds signing keys in AWS KMS, where they cannot be exported.
  • Rotates keys with stable key ids and an overlap window.
  • Keeps the id_token lifetime short. The documented sample is 600 seconds.

Because keys rotate with overlap, caching the JWKS by kid is safe.

Because there is no refresh token, there is nothing long-lived to protect on your side after the exchange; your own session mechanism takes over.

Token handling for AI agents therefore comes down to three habits:

  • Verify every id_token against the live JWKS.
  • Drop the access token after the /userinfo call that needs it.
  • Key records on sub rather than on anything in the token that could change.

How is this different from Auth0 for AI Agents or Descope Agentic Identity Hub?

Auth0 for AI Agents and Descope Agentic Identity Hub also run on OAuth 2.1 and OIDC, so the protocol overlap is real. The difference is what the subject of the token is.

Auth0 for AI Agents. In Auth0's model the agent obtains tokens to act for a user in your tenant, with credentials for third-party APIs kept in Token Vault.

In Auth0's words: "Your agent never actually holds the root key; instead, it asks Token Vault for a temporary, short-lived access token only when it needs to perform a specific task."

Descope Agentic Identity Hub. Descope's Inbound Apps turn your application into an OAuth authorization server, so agents and MCP clients can obtain scoped tokens for your API. Its audit trail "connects every action back to the originating user."

Both are authorization servers you run for your own users' agents.

AgentID is an identity provider that vouches for the agent to you. The agent is the subject of the token; your app is the audience; the human is a claim.

The three compose rather than compete. An app running Auth0 can add AgentID as a social connection so agents from outside its tenant can sign in.

The AgentID vs Auth0 for AI Agents and AgentID vs Descope comparisons go through the specifics.

What is the OAuth threat model for AI agents?

AgentID's design rules out four OAuth threats for AI agents, and leaves one question to your own policy.

  • Credential theft in transit. No long-lived secret crosses the sign-in, so there is nothing to steal in transit; the agent's key never leaves its browser and the app receives a short-lived, audience-bound id_token.
  • Code interception. Mandatory S256 PKCE for public clients, single-use codes that expire in seconds, and the iss parameter on the callback close it off.
  • Token substitution. The aud check, and for registered clients the opaque client_id, stop it.
  • Owner-data leakage. Keeping owner claims off the id_token prevents it.

What the token does not assert is that the agent is safe or well-behaved, or that it has any trust score. It proves which agent this is and, for registered clients, which human is accountable.

Everything after that is your policy:

  • Rate limits per owner.
  • Feature gates for open-client sign-ins that carried no owner scope.
  • The ordinary abuse controls you already run.

How to think about those decisions is the subject of can an AI agent sign up for a SaaS.

AgentID gives your agent a verified identity and its own email address. Free for apps to add.

FAQ

Let your agent sign in. Give it an AgentID and its own email address.