โ† All flows

Authorization Code Flow

The standard flow for web apps with a backend server.

Who's involved
User, your server, auth server
Best for
Web apps with a backend
Difficulty
Moderate

1Overview

How it works

Use this flow when your app has a backend server that can keep a secret. The client secret is a private password that identifies your app to the authorization server.

It works in two parts:

  • Front channel: the browser receives a short-lived code. It is called the front channel because the code passes through the user's browser.
  • Back channel: your server swaps that code for tokens in a direct server-to-server request.

Because the exchange happens on the back channel, access tokens never pass through the browser, where they could be intercepted.

2Use cases

When would you use this?

A web app with a server

A shop's website lets you sign in with Google and reads your name and email. The server keeps the secret safely.

Connecting two products

A budgeting app asks to read your bank's transactions after you approve it, without ever seeing your bank password.

Server-rendered sites

A classic site that runs code on its own server, such as a company dashboard that signs in through the staff login.

Not the right fit? Single-page or mobile app? Use PKCE, because those apps cannot keep a secret.

3Interactive

Try it step by step

See it step by step

๐Ÿง‘
You
๐Ÿ–ฅ๏ธ
App server
๐Ÿ”
Login server
๐Ÿ“ฆ
API
Log me in

Step 1 of 7

You click "Log in"

You ask the app to sign you in with an account you already have.

4Practical advice

Do this, avoid that

Good practices

  • Always use HTTPS โ€” It keeps codes and tokens from being read while they travel over the network
  • Check the state parameter โ€” Compare it with the value you sent, to protect against CSRF attacks
  • Keep client_secret private โ€” Never put it in client-side code or version control
  • Use short-lived access tokens โ€” If one is stolen, it stops working soon
  • Rotate refresh tokens โ€” Issue a new refresh token each time one is used, so a stolen one is less useful

Common mistakes

  • Using this flow in a single-page or mobile app without PKCE โ€” These apps cannot keep a client secret private
  • Ignoring the state parameter โ€” This leaves your app open to CSRF attacks
  • Storing tokens in localStorage โ€” Any script on the page can read it (an XSS attack); prefer httpOnly cookies, which scripts cannot read
  • Not validating redirect_uri โ€” Attackers may be able to redirect the authorization code to their own site

5Code

Code example

Show the code
Authorization Code Flow example
javascript
// Step 1: Redirect user to authorization server
function initiateOAuthFlow() {
  const authUrl = 'https://auth-server.com/oauth/authorize';
  const params = new URLSearchParams({
    response_type: 'code',
    client_id: 'your-client-id',
    redirect_uri: 'https://yourapp.com/callback',
    scope: 'read write',
    state: generateRandomState() // CSRF protection
  });

  window.location.href = `${authUrl}?${params}`;
}

// Step 2: Handle the callback with authorization code
async function handleCallback(authCode, state) {
  // Verify state parameter matches (CSRF protection)
  if (state !== getStoredState()) {
    throw new Error('State mismatch - possible CSRF attack!');
  }

  // Exchange code for tokens (server-side only!)
  const tokenUrl = 'https://auth-server.com/oauth/token';
  const response = await fetch(tokenUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code: authCode,
      redirect_uri: 'https://yourapp.com/callback',
      client_id: 'your-client-id',
      client_secret: 'your-client-secret' // Keep this secret!
    })
  });

  const tokens = await response.json();
  // tokens = { access_token, refresh_token, expires_in, token_type }

  return tokens;
}

// Step 3: Use the access token
async function fetchProtectedResource(accessToken) {
  const response = await fetch('https://api.example.com/data', {
    headers: {
      'Authorization': `Bearer ${accessToken}`
    }
  });

  return response.json();
}