HTTP APIs

larsggu.meReference › OAuth 2.0 authorization code

OAuth 2.0 authorization code

GET /authorize -> code -> POST /token -> access_token

The OAuth 2.0 flow used when a human authorises an application to act on their behalf, exchanging a short-lived code at the authorisation server for a token.

Description

The authorization code flow exists to keep a user's credential away from the application that wants to act for them. The application never sees the password. It sends the user to the authorisation server, the user authenticates there and consents to a named set of scopes, and the authorisation server sends the user back carrying a short-lived code.

That code is deliberately close to worthless on its own. It is single-use, it expires in well under a minute, and it is redeemed over a direct back-channel request in which the application proves it is the client the code was issued to. What comes back is an access token with a lifetime measured in minutes to hours, and ordinarily a refresh token that obtains new access tokens without involving the user again.

The state parameter is not optional in practice. It ties the redirect back to the request that started it, and an application that does not compare the returned state against the one it generated will accept a code it did not ask for. The proof-key extension published as RFC 7636 addresses the related problem of a code being intercepted before redemption, by making the redemption depend on a secret the application generated at the start.

Scopes are the part of this flow that leaks into the rest of an integration. A token carries the scopes consented to, not the scopes the application would like, so an integration that later needs a broader permission has to send the user back through consent rather than widening the token it holds.

Sequence diagram of the authorization code flow: the application redirects the user to the authorisation server, the user consents, a code returns through the browser, and the application exchanges that code for a token over a direct request.

The code travels through the browser; the token never does.

Fields

Fields of OAuth 2.0 authorization code
FieldFormMeaning
response_typecodeNames the flow at the authorisation endpoint.
client_idstringIdentifies the application. Public; not a secret.
redirect_uriabsolute URLMust match a value registered in advance, compared exactly rather than by prefix.
scopespace-delimited listThe permissions being requested. What is granted may be narrower.
stateopaque stringTies the redirect to the originating request. Compared on return.
code_verifierhigh-entropy stringThe proof-key secret, sent at redemption to match the challenge sent at authorisation.

Example

Redirect and redemption

GET /authorize?response_type=code&client_id=app_19
  &redirect_uri=https%3A%2F%2Fapp.example%2Fcb&scope=invoices.read
  &state=Xk91pd&code_challenge=Ae1...9c&code_challenge_method=S256

302 Found -> https://app.example/cb?code=ac_77b2&state=Xk91pd

POST /token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=ac_77b2&client_id=app_19
&redirect_uri=https%3A%2F%2Fapp.example%2Fcb&code_verifier=n4T...12

HTTP/1.1 200 OK
{"access_token":"at_...","token_type":"Bearer","expires_in":3600,
 "refresh_token":"rt_...","scope":"invoices.read"}

The code is redeemed once, over the back channel, and the granted scope is echoed in the token response.

Failure modes

  • Not comparing the returned state, which accepts a code the application did not request.
  • Matching the redirect URI by prefix rather than exactly, which admits a redirect the registration did not authorise.
  • Storing the refresh token where the browser can read it, which puts a long-lived credential somewhere a short-lived one was the point.
  • Requesting every scope at first contact, which produces a consent screen users decline.

Topic: Identity. Last modified 2026-09-06.