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
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
stateparameter โ Compare it with the value you sent, to protect against CSRF attacks - Keep
client_secretprivate โ 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
stateparameter โ This leaves your app open to CSRF attacks - Storing tokens in
localStorageโ Any script on the page can read it (an XSS attack); preferhttpOnlycookies, 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
// 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();
}