Skip to main content

Authorization Code Flow

The Authorization Code flow is the most common OAuth2 flow for web applications. It involves redirecting the user to the authorization server, where they log in and grant consent, then redirecting back with an authorization code that can be exchanged for tokens.

When to Use

  • Server-side web applications that can securely store client secrets
  • Applications where the token exchange happens on the backend

For public clients (SPAs, mobile apps), use the Authorization Code + PKCE flow instead.

Flow Diagram

┌──────────┐                              ┌──────────────┐                              ┌──────────┐
│ User │ │ Your App │ │ S-Auth │
└────┬─────┘ └──────┬───────┘ └────┬─────┘
│ │ │
│ 1. Click "Login" │ │
│ ─────────────────────────────────────────>│ │
│ │ │
│ │ 2. Redirect to /authorize │
│ <─────────────────────────────────────────────────────────────────────────────────────│
│ │ │
│ 3. Login & Grant Consent │ │
│ ─────────────────────────────────────────────────────────────────────────────────────>│
│ │ │
│ 4. Redirect with code │ │
│ <─────────────────────────────────────────────────────────────────────────────────────│
│ │ │
│ │ 5. Exchange code for tokens │
│ │ ─────────────────────────────────────────>│
│ │ │
│ │ 6. Return tokens │
│ │ <─────────────────────────────────────────│
│ │ │
│ 7. Logged in! │ │
│ <─────────────────────────────────────────│ │
│ │ │

Step 1: Authorization Request

Redirect the user to the authorization endpoint:

GET https://auth.sebbyk.net/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=https://yourapp.com/callback&
scope=openid%20profile%20email&
state=RANDOM_STATE_VALUE

Parameters

ParameterRequiredDescription
response_typeYesMust be code
client_idYesYour application's client ID
redirect_uriYesMust match a registered redirect URI
scopeNoSpace-separated list of scopes
stateRecommendedRandom value to prevent CSRF

What Happens

  1. S-Auth checks if the user is logged in
  2. If not, redirects to login page
  3. After login, shows consent screen (if first authorization)
  4. Generates authorization code
  5. Redirects to your redirect_uri with the code

Step 2: Authorization Response

After the user grants consent, S-Auth redirects back to your application:

https://yourapp.com/callback?
code=AUTH_CODE&
state=RANDOM_STATE_VALUE

Always verify the state parameter matches what you sent!

Error Response

If something goes wrong:

https://yourapp.com/callback?
error=access_denied&
error_description=User%20denied%20consent&
state=RANDOM_STATE_VALUE

Step 3: Token Exchange

Exchange the authorization code for tokens:

curl -X POST https://auth.sebbyk.net/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Authorization: Basic BASE64(client_id:client_secret)" \
-d "grant_type=authorization_code" \
-d "code=AUTH_CODE" \
-d "redirect_uri=https://yourapp.com/callback"

Request Parameters

ParameterRequiredDescription
grant_typeYesMust be authorization_code
codeYesThe authorization code received
redirect_uriYesMust match the original request

Authentication

Use one of:

Client Secret Basic (Recommended):

Authorization: Basic BASE64(client_id:client_secret)

Client Secret Post:

client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET

Step 4: Token Response

{
"access_token": "sat_abc123...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "srt_xyz789...",
"scope": "openid profile email"
}

Using the Access Token

Include the access token in API requests:

curl https://auth.sebbyk.net/userinfo \
-H "Authorization: Bearer sat_abc123..."

Example: Node.js Implementation

import express from 'express';

const app = express();

// Step 1: Redirect to authorization
app.get('/login', (req, res) => {
const state = crypto.randomUUID();
req.session.oauthState = state;

const params = new URLSearchParams({
response_type: 'code',
client_id: process.env.CLIENT_ID,
redirect_uri: 'https://yourapp.com/callback',
scope: 'openid profile email',
state,
});

res.redirect(`https://auth.sebbyk.net/authorize?${params}`);
});

// Step 2 & 3: Handle callback and exchange code
app.get('/callback', async (req, res) => {
const { code, state, error } = req.query;

// Verify state
if (state !== req.session.oauthState) {
return res.status(400).send('Invalid state');
}

if (error) {
return res.status(400).send(`OAuth error: ${error}`);
}

// Exchange code for tokens
const response = await fetch('https://auth.sebbyk.net/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Basic ${Buffer.from(
`${process.env.CLIENT_ID}:${process.env.CLIENT_SECRET}`
).toString('base64')}`,
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: 'https://yourapp.com/callback',
}),
});

const tokens = await response.json();

// Store tokens and create session
req.session.accessToken = tokens.access_token;
req.session.refreshToken = tokens.refresh_token;

res.redirect('/dashboard');
});

Security Considerations

  1. Always use HTTPS in production
  2. Validate the state parameter to prevent CSRF attacks
  3. Keep client secrets secure - never expose in frontend code
  4. Use short-lived authorization codes - they expire in 10 minutes
  5. Store tokens securely - use HttpOnly cookies or secure server-side storage