Expected Answer (OAuth 2.0 / RFC 9700)
OAuth 2.0 delegates authorization: a user authorizes a client to access a resource server, and the authorization server issues an access token with a limited scope and lifetime. For browser, mobile, and other public clients, use the authorization code grant with PKCE. The client creates a high-entropy code_verifier, derives a SHA-256 code_challenge, and sends the challenge with the authorization request. After the user authenticates and consents, the authorization server redirects to an exact registered redirect URI with a short-lived authorization code. The client redeems that code only by presenting the original verifier at the token endpoint.
PKCE means an attacker who intercepts the redirect code cannot redeem it without the verifier. Also generate and validate a state value to bind the callback to the initiating browser session and prevent request-forgery or login-CSRF attacks. Use exact redirect URI matching, HTTPS, short token lifetimes, narrow scopes, and the current OAuth security guidance. Do not use the implicit grant or put a client secret in a SPA or mobile app: public clients cannot keep it secret.
Why It Matters
A leaked authorization code, permissive redirect URI, or missing state check can let an attacker obtain a user’s token or attach the attacker’s account to a victim’s session. PKCE and strict redirect validation are core protections for modern public clients.
Example Code
function base64Url(bytes: Uint8Array): string {
return btoa(String.fromCharCode(...bytes))
.replaceAll('+', '-')
.replaceAll('/', '_')
.replace(/=+$/, '');
}
async function beginLogin(): Promise<void> {
const verifier = base64Url(crypto.getRandomValues(new Uint8Array(32)));
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));
const state = crypto.randomUUID();
sessionStorage.setItem('oauth_verifier', verifier);
sessionStorage.setItem('oauth_state', state);
const authorizationUrl = new URL('https://issuer.example/authorize');
authorizationUrl.search = new URLSearchParams({
response_type: 'code', client_id: 'web-client', redirect_uri: location.origin + '/callback',
scope: 'openid profile', state, code_challenge: base64Url(new Uint8Array(digest)),
code_challenge_method: 'S256',
}).toString();
location.assign(authorizationUrl);
}
Common Mistakes
- Using a client secret in a browser application: Anyone can extract it from the delivered code; use PKCE rather than pretending the public client is confidential.
- Skipping
state validation: A callback can then be associated with the wrong initiating browser session.
- Allowing wildcard redirect URIs: An attacker can register or control a matching destination and receive authorization responses.
Follow-up Questions
- What does PKCE add to authorization code flow? (Answer: It binds code redemption to a verifier generated by the same client that initiated the request.)
- What is the difference between an access token and a refresh token? (Answer: An access token authorizes API calls; a refresh token is a more protected credential used to obtain new access tokens.)
References