Skip to content
API Design Learn/Identity, Tokens & Access
Browsing as a guest. Sign in to save your progress and earn XP as you complete chapters.

OAuth2 in Depth: PKCE, Scopes and Refresh Tokens

6 min read

You'll learn to

  • -Explain what PKCE protects against and how the verifier and challenge work
  • -Design scopes and a consent screen that grant the least privilege needed
  • -Rotate refresh tokens and detect a stolen one through reuse
  • -Validate redirect URIs exactly and use the state parameter

The authorization code flow from the Developer Platforms chapter is the right shape, but a few details decide whether it is actually safe. Mobile apps and single-page apps cannot keep a client secret, an authorization code can be stolen in transit, and a long-lived refresh token is a juicy target. This chapter covers the parts an authorization server has to get right.

An Analogy: A Claim Ticket With a Secret Word

You leave your car with a valet and get a claim ticket. If anyone who finds the ticket can collect the car, a stolen ticket is a stolen car. Now suppose you also write a secret word on the stub the valet keeps, and you must say that word to collect. A thief with only the ticket gets nowhere. PKCE is that secret word for the authorization code.

The Problem PKCE Solves

In the code flow, the authorization server redirects the browser back to the app with a one-time code in the URL. On a phone, another installed app can register the same redirect address and read that URL. A public client has no client secret to prove that it is the app that asked for the code, so the thief could swap the code for a token. PKCE (Proof Key for Code Exchange, RFC 7636) closes this gap, and the OAuth 2.0 security best current practice (RFC 9700) asks for it with every kind of client.

How PKCE Works

  • -The app creates a random secret, the code_verifier, and keeps it in memory.
  • -It sends the code_challenge, the base64url-encoded SHA-256 hash of the verifier, with the authorize request.
  • -The server stores the challenge next to the code it issues.
  • -When the app exchanges the code, it sends the original verifier. The server hashes it and compares it with the stored challenge.

Here are the four hops in order. Each picture shows one hop, so you can see what travels, and which secret never leaves the app until the very last step.

1The app starts the flow
Mobile app
Authorization server

It keeps the verifier to itself and sends only the hash of it.

Mobile appAuthorization server- authorize + code_challenge
2The server sends back a one-time code
Authorization server
Mobile app

A thief who reads the redirect sees only the code, not the verifier.

Authorization serverMobile app- redirect with code
3The app redeems the code
Mobile app
Authorization server

The server hashes the verifier and compares it with the stored challenge.

Mobile appAuthorization server- code + code_verifier
4Tokens, then the API call
Authorization server
Mobile app
Photos API

The access token is short-lived. The refresh token is rotated on every use.

Authorization serverMobile app- access + refresh tokenMobile appPhotos API- Authorization: Bearer ...

Because the hash cannot be reversed, an attacker who saw the challenge and the code still cannot produce the verifier. The block below uses the example from the RFC, so you can check the challenge against the standard. Press Run.

Press Run: the RFC's example challenge, and a code that only the holder of the verifier can redeem

A scope is a named permission, such as photos.read or payments.write. The app asks for the scopes it needs, the user sees them on a consent screen in plain words, and the access token carries the scopes that were actually granted, which may be fewer than requested. Ask for the minimum, split read from write, and ask for extra scopes at the moment a feature needs them, not all at sign-up.

A scope is a ceiling, not a permission slip for everything. The API must still check, on every call, that the token has the scope the endpoint needs and that the user owns the object being touched. A token with photos.read may read its own owner's photos and nobody else's.

Short Access Tokens and Rotating Refresh Tokens

Access tokens live for minutes, so a leak expires quickly. A refresh token lives longer and is used only to get new access tokens. To limit the damage of a stolen refresh token, rotate it: every time it is used, the server issues a new refresh token and invalidates the old one. All the tokens from one login form a family. If an old token is ever presented again, someone copied it, so the server revokes the whole family and forces a fresh sign-in.

Press Run: normal use rotates the token, and a replayed old token revokes the whole family

The first call works and hands out sam-phone-r2. The second presents the old token again, which is the signature of a copy, so the family is revoked. The third shows the cost: even the genuine newest token no longer works, and the real user signs in again. That inconvenience is the price of stopping a thief who holds a copy. For public clients, the best current practice accepts either rotation or tokens bound to the client's own key.

Now try the same rules by hand. Refresh a few times as the real app, then play the thief by replaying the first token, and watch every token in the family change state, including the newest one.

Rotate Refresh Tokens, Catch a Replay

Each time the app refreshes, the server hands out a new refresh token and spends the old one. Press the real app's button a few times, then let a thief replay the first token and watch what happens to every token in the family.

r1 valid
  • No requests yet.

Only the newest token is valid. A token that has been used once is spent, so a copy of an old one is useless unless the server can be fooled, and the moment it is presented the server finds out.

Redirect URIs and the State Parameter

  • -Register each redirect URI in advance and match it exactly, character for character. A pattern or a prefix match lets an attacker steer the code to their own address.
  • -Send a random state value with the authorize request and check that it comes back unchanged, so a forged callback from another site is rejected.
  • -Never put tokens in URLs that get logged. The authorization code is short-lived and single-use for exactly that reason.

When you design an authorization server, the checklist is short: PKCE for every client, exact redirect URI matching, narrow scopes with readable consent, short access tokens, and rotating refresh tokens with reuse detection.

Interview Signal is part of Pro

See a real weak answer next to a real strong one for this exact topic.

Quiz is part of Pro

Test what you just read with a short quiz, and bank the XP.

Ready to Build This?

Authorization Server and Scopes and Consent in the API Design Lab.

Discussion0

Join the Discussion

Sign in to leave comments, reply to others, or like insights.

Sign In to ScaleDojo

No comments yet. Be the first to start the thread!