OAuth 2.0 Authorization Framework
The foundation under MCP authorization and nearly every agent delegation scheme.
Flows / Human present
The baseline. A person signs in, approves specific scopes, and the agent receives a token that says so.
This is the flow almost everything else on this site builds on or stands in for. The user is sent to an authorization server they already trust, signs in there, and approves what the agent may do. The agent never sees a password; it gets a short-lived access token scoped to one resource.
This page shows the flow as published: the OAuth 2.0 authorization code grant (RFC 6749) with PKCE (RFC 7636), run the way the security best current practice (RFC 9700) sets out. That means PKCE, which it requires of a public client like this one; exact redirect URI matching; a token restricted to one audience; and no implicit or password grant. If an agent acts for a person and that person is at the keyboard, start here.
11 steps between 4 parties. Each step shows the message and who holds which credential afterward. Use ←→ or select any arrow.
Drag to see every party and step. Tap a step to jump to its explanation.
The agent has no credential for the calendar API. It could ask for one up front, but the better habit is to wait until a task actually needs it: the consent screen then matches something the user just asked for.
The agent generates a random code_verifier, hashes it into a code_challenge, and creates a state value. The verifier stays with the agent. Only the hash travels through the browser, so an authorization code stolen in transit is useless without it.
code_verifier = random(43–128 chars)
code_challenge = BASE64URL(SHA256(code_verifier))
state = random()Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
Everything in this request is visible to the browser, so it carries no secrets: who the client is, where to send the user back, what is being asked for, and which resource the token is for. client_id is the identifier the authorization server gave this client when it was registered. The resource parameter matters more for agents than for classic apps, because one agent talks to many servers and a token must be good for exactly one.
GET /authorize?response_type=code
&client_id=s6BhdRkqt3
&redirect_uri=http://127.0.0.1:52100/callback
&scope=calendar.read
&resource=https://calendar.example/api
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
&state=af0ifjsldkj HTTP/1.1
Host: auth.exampleWho holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
The user signs in with whatever the authorization server requires: a passkey, SSO, a second factor. The agent is not part of this conversation. Then the server shows what is being requested and by whom. This screen is the only place a human sees the scopes in plain language, which is why its wording deserves more care than it usually gets.
Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
Consent is recorded at the authorization server, tied to this user, this client and these scopes. That record is what lets the user (or an admin) see and revoke the grant later without involving the agent.
Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
The code is single-use and expires in about a minute. The agent checks that state matches what it sent and, where the server provides it, that iss matches the authorization server it meant to talk to. That second check defeats mix-up attacks, which are a real risk for agents that deal with many authorization servers.
GET /callback?code=SplxlOBeZQQYbYS6WxSbIA
&state=af0ifjsldkj
&iss=https://auth.example HTTP/1.1
Host: 127.0.0.1:52100Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
This call goes straight from the agent to the token endpoint, not through the browser. The agent presents the code and the original code_verifier. The server hashes the verifier and compares it with the challenge from step 3; only the party that started the flow can finish it. redirect_uri is sent again because RFC 6749 requires it here whenever the authorization request carried one.
POST /token HTTP/1.1
Host: auth.example
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=http://127.0.0.1:52100/callback
&client_id=s6BhdRkqt3
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
&resource=https://calendar.example/apiWho holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
The access token is short-lived and bound to one audience. The refresh token, if issued, lets the agent get new access tokens without the user. For public clients the security best current practice (RFC 9700) requires refresh tokens to be either sender-constrained or rotated on use.
{
"access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCJ9…",
"token_type": "Bearer",
"expires_in": 600,
"refresh_token": "8xLOxBtZp8",
"scope": "calendar.read"
}Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
The token goes in the Authorization header and nowhere else. Never in a URL, and never into the model’s context window: a token the model can read is a token a prompt injection can exfiltrate.
GET /api/events?from=today HTTP/1.1
Host: calendar.example
Authorization: Bearer eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCJ9…Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
Signature, issuer, expiry, scope, and above all audience. An API that accepts tokens minted for some other service becomes a confused deputy. If the token is for https://calendar.example/api and this is not that, reject it.
Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
The token in this example is a JWT access token (RFC 9068), so the API can read which user it is for (sub) and which client it was issued to (client_id). What it does not know from a plain access token is that an AI agent was in the middle, or which model run decided to make the call. Flows further down this site add that information.
Who holds what after this step
Parties not listed hold none of this flow’s credentials at this point.
mail.readwrite can do anything the user can do to mail.OAuth 2.1 (draft-ietf-oauth-v2-1) is an Internet-Draft of the IETF OAuth working group, at revision -17 dated October 30, 2026. It is not an RFC. It gathers RFC 6749, RFC 6750, RFC 7636, RFC 8252, RFC 9700 and RFC 10017 into one document and changes some behavior as it does.
Two of its changes touch this page. A token request no longer carries redirect_uri, and the PKCE plain method is removed. This page shows the published RFCs, so step 7 still sends redirect_uri. A flow built on the draft, such as MCP authorization, leaves it out.
See the full tracker for status and links.
What the flow implements, shows in a step, or must support. These decide its maturity rating.
The foundation under MCP authorization and nearly every agent delegation scheme.
Mandatory for MCP clients, which must also refuse to proceed if a server does not advertise support.
The minimum bar for any agent acting as an OAuth client.
Required by MCP. An agent talks to many servers; this is what stops a token for one being replayed at another.
The defense against mix-up attacks for clients that use many authorization servers. Clients must validate it under MCP 2026-07-28.
Governs desktop agents, IDE extensions and CLIs, which are native public clients.
The default way agents present tokens, and the thing proof-of-possession schemes are trying to replace.
The usual carrier for act and other agent-related claims.
Related reading. Not what the flow implements, and not used in a step.
The baseline MCP profiles. Worth knowing that the normative base of most agent authorization is still, formally, a draft.
How an MCP client that has never seen a server before discovers where to sign in, identifies itself, and gets a token good for that server alone.
For agents with no browser of their own: show a code, let the person approve on another device, and poll.
Bind a token to a key so that stealing the token is not enough to use it.
Start with a weak token. When the agent reaches something sensitive, the API says so and the user is asked for exactly that.