OAuth gives the app a pass, never your password.
You type your password only on the bank’s page. Its authorization server gives the app a token for transactions.read, and the bank’s API checks it.
Is an access token a login?
Bad: Log in its user
It’s for the API. A quiz app holding your token can replay it.
Good: Check an ID token
aud names your app; nonce names this login.
Only a single-use code crosses the browser.
Redirect URLs leak into history, logs and Referer. So the browser carries a short-lived code, and your server swaps it for tokens directly.
PKCE makes a stolen code worthless.
The app keeps a random verifier and sends only its SHA-256 hash. The token endpoint hashes the verifier and compares. A thief has the code, never the verifier.
Your client secret stops a thief redeeming a stolen code. So why PKCE?
The trick
Code injection: the attacker pastes it into their own login; your server redeems it with your secret. PKCE’s verifier won’t match.
Pick the grant by who is there
- User in a browser or app → code + PKCE
- No user, a nightly job → client credentials
- No keyboard, a smart TV → device grant
- Implicit and password grants → retired
12×
the refresh load, for a 12× shorter window
With local JWT checks, lifetime is revocation lag. For 1,000,000 active users, 5-minute tokens cost ≈ 3,333 refreshes/s; 60-minute ones, ≈ 278/s.
Remember
User present? Code flow with PKCE, so the browser carries only a single-use code. The access token is for the API; the ID token is for your app. Never log in with an access token.