How OAuth 2.0 Works: PKCE Flow
OAuth 2.0 explained: authorization code flow, PKCE for SPAs, access tokens, and secure implementation.
What Is OAuth 2.0?
OAuth 2.0 is an open standard authorization protocol that allows users to grant applications access to their data without sharing passwords. It's the industry standard for "Sign in with Google/Facebook/GitHub."
Key benefit: Users never share their password with third-party applications. The authorization server handles authentication securely.
OAuth 2.0 Authorization Code Flow
User clicks 'Login with Google'
Browser redirects to authorization server
User authenticates and consents
Enters credentials, approves permission scope
Authorization code returned
Server redirects to app callback URL with code
Exchange code for token
Backend server securely exchanges code for access token
Access API resources
App uses access token to fetch user data from API
PKCE (Proof Key for Code Exchange)
PKCE is an OAuth 2.0 extension that protects against authorization code interception attacks. Essential for single-page applications (SPAs) and mobile apps.
How it works:
- 1. App generates random code_verifier
- 2. Creates code_challenge = SHA256(code_verifier)
- 3. Sends code_challenge to authorization server
- 4. When exchanging code, sends original code_verifier
- 5. Server verifies: SHA256(verifier) == challenge
Access Tokens vs Refresh Tokens
Access Token
- • Short-lived (15 min to 1 hour)
- • Grants access to protected resources
- • Used in Authorization header
- • Expires quickly for security
Refresh Token
- • Long-lived (days to months)
- • Used to get new access tokens
- • Should be stored securely (httpOnly cookie)
- • Never exposed to browser JavaScript
OAuth 2.0 Implementation Flow Detail
Step 1-3: Frontend Initiates Login
// Generate PKCE values
const codeVerifier = generateRandomString(128);
const codeChallenge = base64url(sha256(codeVerifier));
// Redirect to authorization server
const authUrl = `https://auth-server.com/authorize? client_id=YOUR_CLIENT_ID& redirect_uri=https://yourapp.com/callback& scope=openid%20profile%20email& code_challenge=${codeChallenge}& code_challenge_method=S256`;
window.location.href = authUrl;Step 4-5: Backend Exchanges Code for Token
// In callback handler (backend)
const tokenResponse = await fetch('https://auth-server.com/token', {
method: 'POST',
body: {
grant_type: 'authorization_code',
code: authCode,
client_id: 'YOUR_CLIENT_ID',
client_secret: 'YOUR_CLIENT_SECRET',
code_verifier: codeVerifier,
redirect_uri: 'https://yourapp.com/callback'
}
});
const { access_token, refresh_token } = await tokenResponse.json();
// Store securely (httpOnly cookie, not localStorage)OAuth 2.0 Grant Types
Authorization Code (PKCE)
Security: ★★★★★
SPAs and mobile apps. User-facing, redirects through browser. Most secure for public clients.
Client Credentials
Security: ★★★
Server-to-server communication. No user involved. Dangerous: requires client_secret.
Resource Owner Password
Security: ★
Legacy apps. User provides password directly to app. Avoid - password exposed to app.
Implicit
Security: ★
Deprecated. Single-page apps without backend. Access token exposed in URL.
Refresh Token
Security: ★★★★
Getting new access token using refresh token. Uses refresh_token grant type.
OAuth 2.0 Security Best Practices
Always use HTTPS
Never transmit tokens or codes over HTTP. Man-in-the-middle attacks can intercept credentials.
Use PKCE for all public clients
Even if you think your app is not public. PKCE protects against authorization code interception.
Store tokens securely
Use httpOnly, secure cookies for tokens. Never store in localStorage (XSS vulnerable).
Validate state parameter
Use state param to prevent CSRF attacks. Server must verify state matches before processing callback.
Set token expiration short
Access tokens: 15 min to 1 hour. Refresh tokens: days to months. Limits exposure if stolen.
Use proper redirect_uri validation
Whitelist callback URLs. Authorization server must verify redirect_uri matches registered URI exactly.
Implement scope limitation
Request minimal scopes needed. Users can see what app is requesting access to.
Revoke tokens when needed
Implement logout that revokes refresh tokens. Prevents reuse if token is compromised.
Common OAuth 2.0 Errors & Solutions
invalid_redirect_uri
Callback URL doesn't match registered URI exactly. Check scheme, domain, port. Must be HTTPS.
invalid_client
client_id or client_secret is wrong/expired. Verify credentials in settings. Regenerate if needed.
access_denied
User clicked 'Deny' on consent screen or doesn't have permission. Not an app error. User action.
invalid_scope
Requested scope doesn't exist or not allowed. Check scope spelling. Verify app has access to scope.
code_expired
Authorization code expires quickly (minutes). Exchange immediately. Don't delay between auth and token exchange.
OAuth 2.0 FAQ
Is OAuth 2.0 the same as OpenID Connect?
No. OAuth 2.0 is authorization (access to data). OpenID Connect adds authentication layer on top of OAuth 2.0 (who you are). OIDC = OAuth 2.0 + ID token.
Can I use OAuth for login only?
Not standard OAuth. Use OpenID Connect instead, which provides ID token for authentication. Standard OAuth only confirms you have user's authorization.
Where should I store the refresh token?
httpOnly, Secure cookie set by backend. Never in localStorage (XSS vulnerable). Never expose to JavaScript. Ideally, rotate on each use.
What if someone steals my refresh token?
They can get new access tokens indefinitely. Implement token rotation: issue new refresh token with each use, invalidate old one. Enables detection of misuse.
Do I need to implement OAuth if I'm a small app?
No. Use for social login convenience. For simple auth, use sessions/JWT directly. OAuth adds complexity, worth it for multi-tenant or federation.
Related Concepts
Related Tools
OAuth 2.0 Debugger
Test OAuth 2.0 flows and debug authorization issues.
JWT Decoder
Decode and inspect JWT tokens for debugging.