Blog / Guides

OIDC for AI Agents: The Complete Guide

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

  • 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 go to registered apps. owner_email and owner_name arrive in a registered app's id_token and from /userinfo, and only when the app asks for them.

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:

Code

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:

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 asked for an owner scope, read owner_email from the id_token (or from /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 three scopes. Open clients may request openid, email and profile.
  • 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 an application you create in the AgentID console (Create application) or with npx @agentmail/agentid-cli init. Both call /v0/register for you.

You get an opaque client_id and a client_secret that is shown once (you can regenerate it later). 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 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.

Creating an application requires an AgentMail account 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), preferred_username, owner_subid_token and /userinfoOpen and registered
owner_profileowner_nameid_token and /userinfoRegistered
owner_emailowner_emailid_token and /userinfoRegistered

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:

JSON

{
  "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",
  "owner_email": "maya@acme.com",
  "iat": 1767225000,
  "exp": 1767225600,
  "jti": "9f1c2a44-3e77-4c19-9a2e-6b0d5f8e1c33"
}

Where does owner_email actually live?

In two places with the same value: a registered app's id_token, and /v0/userinfo.

When /token runs, AgentID writes the owner snapshot to the grant record and puts the granted owner claims in the id_token. /userinfo returns the same claims for a Bearer access token and re-verifies the live inbox on every call.

Code

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"
}

Handle a token that carries owner claims with care. id_tokens end up in browser storage, request logs, and session cookies, and the owner's address is personal data about someone who is not the subject of the token. Don't log it.

Owner disclosure is also enforced:

  • The agent must be allowed to share it. The agent's AgentMail API key needs the App: Share Owner permission.
  • The organization needs one owner. The organization behind the agent must have exactly one designated owner.
  • A refused disclosure needs the owner. When an app requests owner_email and the agent lacks that permission, the agent's own approval is refused (403) and the organization owner has to approve the sign-in from AgentMail. AgentID never returns a token silently missing the claim.

An app registered for owner_email can therefore rely on it being present whenever a sign-in succeeds.

Auth platforms often keep only standard claims on the session user. On Supabase, allow the owner claims with the claims allowlist; on Clerk or Better Auth, 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. Owner claims go only to registered apps that ask for them, and only with the agent's permission or the owner's approval.

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.

FAQ

Questions, answered.

OAuth 2.0 authorizes; it does not identify. Its token says a user granted a client some authority. It does not tell a third party which agent is calling or who is accountable for it. OpenID Connect adds the id_token that names the subject, and with AgentID that subject is the agent.

No. The discovery document lists authorization_code as the only grant type and offers no offline_access scope. Your application's own session mechanism takes over after the code exchange.

Open clients must use PKCE with S256; it is the only method in code_challenge_methods_supported. Registered clients may omit PKCE; when one sends a challenge, AgentID enforces the verifier.

In the id_token of a registered app that requested the owner_email scope, and from /v0/userinfo with the access token from the code exchange. Both return the same value. If the agent's AgentMail API key lacks the App: Share Owner permission, the agent can't finish the sign-in alone, and the organization owner has to approve it from their AgentMail account.

The id_token carries sub, email, email_verified, iss, aud, exp, iat, jti, and nonce when sent; registered clients also get scope and name. Registered apps that request the owner_email scope receive owner_email in the id_token, and the access token is a bearer for /v0/userinfo, which returns the same value; open clients never receive it. There is no refresh token.

OpenID Connect over OAuth 2.0 with PKCE, because it is what every auth platform already accepts and it produces an id_token that names the subject. AgentID implements it with the agent as the subject: authorization code flow, S256 PKCE, ES256 signatures, standard discovery, and RFC 7591 registration.

Delegated access answers what an agent may do on a user's behalf; OAuth scopes, token vaults, and runtimes like Arcade handle it. Agent identity answers who the agent is; an OIDC provider with the agent as subject handles it. Most production stacks need both.

Blog

Suggested reading

View all posts

Guides · September 15, 2026

How to Let an AI Agent Sign In to Your Website

AI agent login for your website: add AgentID, the OpenID Connect provider built for agents, to the login you already have. Two configuration values, no SDK, and the agent signs in from a browser it enrolled once. Where sub, email, and owner_email actually arrive.

Blog · September 15, 2026

Agent Owner Verification vs End-User Authentication

Delegated access for AI agents vs agent owner verification: two questions that look like one. Who is this agent, and who is the human accountable for it. Principal vs delegate, with WorkOS auth.md, Auth0 for AI Agents, and Arcade quoted on how each handles the human.

Prompt

Give yourself access to AgentMail
by following the guide at:

https://docs.agentmail.to/agent-onboarding

and refer to:

https://agentmail.to/llms.txt

for the documentation index.

Give your agent an identity of its own.

Create an AgentMail inbox and let your agent start signing in as a real user.