You have seen this before
An app sends you to Google. Google asks whether the app may see your calendar. You click Allow and land back in the app. That is the routine this lesson takes apart, and it is called OAuth.
In that example Google’s sign-in page is the authorization server, the Google Calendar API is the API, and the app is the client. Inside a company, the authorization server is usually whatever employees already sign in with. A company can also take the decision away from Alice: an administrator decides which agents may reach which applications, and she is shown no approval screen. That is a flow of its own, cross-app access. In this lesson, Alice is asked. A library does most of the work in both cases, which is how so many of us have shipped this without ever seeing the steps.
For an agent the steps are worth seeing once. Each exists to close a particular hole, and an agent leans on all of them.
The exchange, step by step
Four parties and eight steps, in four parts:
- Alice asks, and the agent sends her to sign in. The agent has no access yet. It sends her browser to the authorization server, and keeps a one-time value to itself.
- Alice signs in and approves, away from the agent. She proves who she is to the authorization server and says yes there. Her approval is recorded there, which is called consent.
- The agent trades a one-time code for a token. Her browser comes back with a code that works once. The agent trades it, together with the value it kept, on a direct connection. That check is called PKCE.
- The agent calls the API. The access token goes in a header, the API runs its five checks, and Alice gets her answer.
The diagram walks through each step. Before you start, guess at which step the agent first holds something it could send to the calendar API.
Sequence diagram4 participants, 8 steps
- Direct call
- Via the user’s browser or device
- Response
Drag to see every party and step. Tap a step to jump to its explanation.
Part 1 of 4Alice asks, and the agent sends her to sign in
Alice asks for something the agent cannot reach yet
The agent has no token for the calendar. Asking for one now, when a task needs it, means the screen Alice is about to see matches something she just asked for.
Part 1 of 4Alice asks, and the agent sends her to sign in
The agent sends Alice’s browser to the authorization server
The agent does not ask Alice for anything. It opens her browser on the authorization server’s own page, with a note in the address: which app is asking, what for, which API the token is for, and where to send her back. This is the jump you have seen behind every “Sign in with” button.
The agent also makes up a random value for this one sign-in and sends only a scrambled version of it, the
code_challenge. It keeps the original, thecode_verifier, to itself. That matters in step 5.GET /authorize?response_type=code &client_id=calendar-assistant &scope=calendar.read &resource=https://calendar.example &redirect_uri=https://assistant.example/callback &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM &code_challenge_method=S256 HTTP/1.1 Host: auth.exampleWho holds what after this step
- Agent
- newcode_verifier
Parties not listed hold none of this flow’s credentials at this point.
Part 2 of 4Alice signs in and approves, away from the agent
Alice signs in and says yes, away from the agent
Alice proves who she is to the authorization server, in whatever way it requires: a password, a passkey, a second factor. The agent is not part of this and sees none of it.
Then the server shows her what is being asked, in plain words, and she approves. That approval is recorded at the authorization server. It is called consent, and Alice can withdraw it there later without going near the agent.
Who holds what after this step
- Agent
- code_verifier
Parties not listed hold none of this flow’s credentials at this point.
Part 3 of 4The agent trades a one-time code for a token
Her browser comes back carrying a one-time code
The authorization server sends Alice’s browser back to the agent with an authorization code. It is not a token. It works once, and only for a short time.
It is weak on purpose. This trip runs through the browser, where addresses end up in history and logs. So what travels here is worth nothing without the value the agent kept.
GET /callback?code=SplxlOBeZQQYbYS6WxSbIA HTTP/1.1 Host: assistant.exampleWho holds what after this step
- Agent
- code_verifiernewauthorization code
Parties not listed hold none of this flow’s credentials at this point.
Part 3 of 4The agent trades a one-time code for a token
The agent trades the code for a token, directly
This call goes straight from the agent to the authorization server, not through the browser. The agent sends the code together with the
code_verifierfrom step 2. The server scrambles it the same way and compares. Only whoever started the sign-in can finish it.That check has a name, PKCE (said “pixie”). Someone who stole the code from the browser has half of what they need, and the half that never left the agent is the half that counts.
Who holds what after this step
- Agent
- used up
authorization codeused upcode_verifier
Parties not listed hold none of this flow’s credentials at this point.
Part 3 of 4The agent trades a one-time code for a token
The authorization server issues the token
Back comes the access token from the previous lesson: about Alice, issued to this app, for the calendar API, read only, good for ten minutes.
Often a second token comes with it. A refresh token is not sent to any API. The agent keeps it and shows it to the authorization server when the access token runs out, to get a new one without sending Alice through sign-in again.
{ "access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCJ9…", "token_type": "Bearer", "expires_in": 600, "refresh_token": "8xLOxBtZp8", "scope": "calendar.read" }Who holds what after this step
- Agent
- newaccess tokennewrefresh token
Parties not listed hold none of this flow’s credentials at this point.
Part 4 of 4The agent calls the API
The agent calls the API
The token goes in the
Authorizationheader, and the API runs its five checks.One habit to form now: the token is held by your program and never shown to the model. Not in the prompt, not in the conversation, not in a log. A token the model can read is a token that text on a web page can ask it to repeat.
GET /events?day=tomorrow HTTP/1.1 Host: calendar.example Authorization: Bearer eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCJ9…Who holds what after this step
- Agent
- access tokenrefresh token
Parties not listed hold none of this flow’s credentials at this point.
Part 4 of 4The agent calls the API
The API answers
Alice gets her answer. The agent never saw her password, and it holds something that can do exactly one thing: read her calendar.
Who holds what after this step
- Agent
- access tokenrefresh token
Parties not listed hold none of this flow’s credentials at this point.
Why so many hops?
Each one answers a question people ask the first time they see this.
Why send Alice to another site? So that her password goes to one place: the service that already has it. An agent that took her password would hold her whole account and not the part the task needs, with no way to take that back short of changing the password. Copies would sit in your database and your logs. And anything typed into a conversation, the model can be asked to repeat by the next page it reads.
So for a service that offers this exchange the rule has no exceptions: an agent never asks a user for their password. Only the authorization server does. Asking would also teach people to type it into whatever asks, which is the habit phishing depends on. OAuth once had a way for an app to collect a password and trade it for a token, and the current security guidance says it must not be used. For a service that offers nothing else, see the final lesson.
Why a code first, and not the token? Because the trip back runs through the browser, and a browser is a leaky place. The code is built to be worthless if it leaks, as long as the authorization server insists on the value the agent kept. Current guidance requires every authorization server to. The token only ever travels on the direct connection in step 6.
Why does the token expire so fast? Because it is a bearer token, and a short life limits what a stolen copy is worth. The refresh token is what makes a short life bearable.
Before the first sign-in
The authorization server has to know the app that is asking. Usually a developer registers it once, in a console, and receives a client ID: the calendar-assistant you saw in the token. Registration also fixes the address the browser may be sent back to, so that a code cannot be delivered to someone else’s server.
Where the app runs changes one detail:
- A hosted app with a backend can keep a secret. It is given a client secret at registration, or registers the public half of a key of its own, and proves which app it is when it trades the code. OAuth calls this a confidential client. Each user’s refresh token stays on the server, filed under that user.
- An app on the user’s machine, such as a desktop app, a command-line tool or an editor extension, cannot keep a secret: anything shipped inside it can be extracted. It is a public client, and PKCE is what it relies on. The value PKCE uses is made up fresh for each sign-in, so there is nothing shipped inside the app to extract.
- An agent with no browser where it runs shows a short code, and Alice approves on her phone or laptop instead. That variation is called device authorization.
When Alice has gone home
The refresh token is what lets an agent keep working after Alice closes the tab. That makes it the most valuable thing the agent holds, and it changes what you have to build:
- Store it where only your code can reach it: encrypted on your server and filed under its user, or in the operating system’s credential store on a desktop. Never in the prompt, and never in a log.
- Expect it to stop working. Alice can withdraw her consent, an administrator can revoke it, and the authorization server may replace it every time it is used.
- Notice what you have built: software holding a person’s access, with that person no longer watching. The next lesson comes back to this.
And MCP?
If your agent connects to a remote MCP server, this same exchange runs. MCP adds two things in front of it, and one rule. The agent may never have heard of the server before, so the server’s first reply points to a document that names its authorization server, and the agent can identify itself by a web address it controls instead of a client ID registered in advance. The rule is that every token is requested for that one server. After that, it is the eight steps above.
One caution comes with it. A server nobody has vetted is telling your agent which addresses to fetch and where to send the user. Connect only to servers the user chose, and show the user which site they are about to sign in to. If your agent runs on your own servers, refuse addresses inside your own network as well.
Check yourself
Answer before you read on. Nothing is recorded.
What to keep
- The user signs in and approves at the authorization server, never in the agent.
- What comes back through the browser is a one-time code. The token itself only ever travels on a direct connection.
- PKCE ties the code to the app that asked for it.
- A refresh token lets the agent carry on without the user. Guard it as if it were the user’s access, because it is.
- Tokens are held by your program and never shown to the model.
One rule
An agent never asks a user for their password. Only the authorization server does.
The names for these
The rest of this guide, and every specification, uses these terms. Each links to its entry in the glossary.
| In this lesson | Its name | Defined as |
|---|---|---|
| The whole routine | OAuth | A framework that lets one program obtain limited access to something, for a person or for itself. When it acts for a person, it does not need that person’s password. OAuth 2.0 is the published standard; OAuth 2.1 is the draft that consolidates it. |
| Alice saying yes, on the record | Consent | A user’s recorded approval, at the authorization server, for a client to act with specified scopes. |
| The one-time code that comes back through the browser | Authorization code | A single-use, short-lived code returned through the browser after a user consents, which the client exchanges for tokens over a direct connection. |
| The agent’s proof that it started the sign-in | PKCE | A one-time secret that ties an authorization code to the client instance that requested it. |
| What gets a new access token without asking Alice | Refresh token | A longer-lived credential used to obtain new access tokens without the user. The thing most worth protecting in an agent runtime. |
| An app that can keep a secret | Confidential client | A client that can keep a secret, because it runs on a server its operator controls. It proves which application it is with a client secret or a key. Contrast with a public client. |
| An app that cannot | Public client | A client that cannot keep a secret, because it runs on a user’s device. Desktop, CLI and browser-based agents are public clients. |
Go deeper
The same ground at full depth, when you want it. None of it is needed for the next lesson.
- Authorization code with PKCEThe same flow at full size: eleven steps, with the HTTP on the wire.
- MCP authorizationThe same flow when the agent has never seen the server before.
- Device authorizationFor an agent with no browser where it runs.