Authorization Code Flow

Authentication Security Notes Jan 6, 2025 JAVASCRIPT

Definition

When you click “Sign in with Google” on a website, have you ever wondered what’s happening behind the scenes? You’re redirected to Google, you log in, you approve some permissions, and suddenly you’re back on the original website, logged in. No password was shared with the website. How does that magic work? That’s the Authorization Code Flow.

The Authorization Code Flow is the most secure way for web applications to authenticate users through third-party providers like Google, Facebook, or your company’s identity system. Here’s the clever part: instead of giving the application your password or even a token directly, you first get a temporary “authorization code” - a short-lived, single-use ticket that the application then exchanges for actual access tokens behind the scenes.

Why the extra step? Security. That initial redirect happens in your browser, which isn’t perfectly secure - browser history, shared computers, network sniffing. By only passing a useless-on-its-own code through the browser, and then exchanging it for real tokens via a secure server-to-server connection, the sensitive tokens never touch your browser. The authorization code is like a coat check ticket - worthless to anyone who doesn’t have access to the coat check counter (your server).

Example

Signing into a Third-Party App: You want to use Canva to design graphics using your Google Photos. You click “Connect Google Photos” and are redirected to Google. You log in (if not already), approve Canva’s access to your photos, and Google sends you back to Canva with an authorization code. Canva’s server exchanges this code for tokens that let it access your photos. You never gave Canva your Google password.

Corporate Single Sign-On: Your company uses Okta for authentication. When you access Salesforce, it redirects you to Okta. You authenticate with Okta (maybe using your company password and 2FA), Okta sends you back to Salesforce with a code. Salesforce’s backend exchanges the code for tokens. Now you’re logged into Salesforce without creating a separate Salesforce password.

Mobile App with Backend: A mobile banking app uses Authorization Code Flow with PKCE. When you log in, the app opens a browser to the bank’s auth page. You enter your credentials there (not in the app), approve access, and get redirected back to the app with a code. The app exchanges this code for tokens. Even if malware is watching the redirect, the code alone is useless without the PKCE secret.

GitHub Integration: When a code editor like VS Code wants to access your GitHub repositories, it uses this flow. You’re sent to GitHub, log in, approve access to specific repos, and get redirected back with a code. VS Code’s auth server exchanges it for tokens. GitHub never sees VS Code, and VS Code never sees your GitHub password.

Analogy

The Coat Check System: When you arrive at a fancy restaurant, you hand your coat to the attendant and get a numbered ticket. The ticket itself is worthless - it’s just a piece of paper with a number. But when you present it to the right attendant (your backend server) with proper identification (your client secret), they give you back your coat (access token). Someone who steals your ticket can’t get your coat because they don’t have access to the coat check counter.

The Doctor’s Referral: When your general doctor refers you to a specialist, they don’t give the specialist your complete medical history directly. Instead, they give you a referral letter (authorization code). You take the letter to the specialist, who then contacts your doctor’s office directly (server-to-server) to get your actual records. The referral letter just proves you were authorized; the sensitive data transfers through secure channels.

The Bank Safe Deposit Box: To access your safe deposit box, you don’t just show up with a key. First, you prove your identity to the clerk (authorization), who gives you an access slip (code). You take the slip to the vault attendant (token endpoint), who verifies it through internal systems and then lets you access your box (gives you tokens). Multiple checkpoints, multiple verifications, maximum security.

The Two-Part Ticket: Imagine a concert where you first receive a claim ticket at the box office after showing ID. Then you take that claim ticket to a separate secure window where they verify the ticket and give you the actual concert pass. Someone who steals your claim ticket can’t get in because the secure window requires additional verification that only you (or your server) can provide.

Diagram

sequenceDiagram
    participant User
    participant App as Client App
    participant Auth as Authorization Server
    participant API as Resource Server

    User->>App: 1. Click "Login"
    App->>Auth: 2. Redirect to /authorize
(client_id, redirect_uri, scope, state) Auth->>User: 3. Show login form User->>Auth: 4. Enter credentials Auth->>User: 5. Show consent screen User->>Auth: 6. Approve permissions Auth->>App: 7. Redirect with authorization code Note over App: Code is single-use,
expires in minutes App->>Auth: 8. Exchange code for tokens
(code + client_secret) Note over App,Auth: Server-to-server call
(secure channel) Auth->>App: 9. Access token + Refresh token App->>API: 10. API request with Bearer token API->>App: 11. Protected resource

Code Example


// Step 1: Redirect to authorization server
const authUrl = 'https://oauth.provider.com/authorize?' +
  'response_type=code&' +
  'client_id=your_client_id&' +
  'redirect_uri=https://yourapp.com/callback&' +
  'scope=read:user&' +
  'state=random_csrf_token';
window.location.href = authUrl;

// Step 2: Handle callback and exchange code for token (server-side)
const tokenResponse = await fetch('https://oauth.provider.com/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: authorizationCode,
    client_id: 'your_client_id',
    client_secret: 'your_client_secret',
    redirect_uri: 'https://yourapp.com/callback'
  })
});

Security Notes

SECURITY NOTES

CRITICAL: Authorization Code Flow adds multiple security layers; each layer must be properly implemented.

Authorization Code Security:

  • Single-use only: Each code can be exchanged exactly once; reuse attempts indicate attack
  • Short expiration: Codes must expire within 10 minutes; prevent long-window token theft
  • Backend exchange only: Exchange codes exclusively on your server, never in browser or mobile app
  • Exact redirect_uri validation: Match redirect_uri exactly; no wildcards or substring matches
  • State parameter validation: Verify state parameter to prevent CSRF attacks; use cryptographically random values

PKCE (Proof Key for Code Exchange):

  • Mandatory for public clients: SPAs, mobile apps, and desktop applications MUST use PKCE
  • Code challenge: Generate code_challenge and include in authorize request
  • Code verifier: Exchange code with matching code_verifier; server validates cryptographic binding
  • Prevents authorization code interception: Even if attacker intercepts code, they can’t exchange it without verifier

Client Credentials Protection:

  • Never expose client secret: Client secret must never appear in browser code, mobile apps, or frontend
  • Backend-only secret: Only backend server stores and uses client secret
  • Secure vault: Store secrets in Vault, AWS Secrets Manager, or equivalent secure systems
  • Never in code: Never hardcode secrets in source files, config files, or logs
  • Rotate regularly: Change client secrets every 90 days or if compromised

Session Management:

  • HTTPS for all redirects: All redirect URLs must use HTTPS; never allow HTTP redirects
  • Session creation: Create proper session after exchanging code for tokens
  • Secure session storage: Use httpOnly, Secure, SameSite cookie flags for session cookies
  • Prevent session fixation: Generate new session IDs after successful authentication

Token Storage:

  • Access tokens: Store securely; use httpOnly cookies or in-memory storage for SPAs
  • Refresh tokens: Store separately with longer TTL; use rotation to limit exposure
  • Never log tokens: Exclude tokens from application logs and monitoring systems

Standards & RFCs