DeveloperSecurity

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

1

User clicks 'Login with Google'

Browser redirects to authorization server

2

User authenticates and consents

Enters credentials, approves permission scope

3

Authorization code returned

Server redirects to app callback URL with code

4

Exchange code for token

Backend server securely exchanges code for access token

5

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.

Open Tool →

JWT Decoder

Decode and inspect JWT tokens for debugging.

Open Tool →