Do not use this in production. This article is for educational purposes. In production, always use battle-tested libraries like passport.js, spring-security-oauth2, or authlib.
The Authorization Code Grant is the most secure OAuth2 flow. Here's how it works:
sequenceDiagram
participant User
participant Client
participant Auth as Auth Server
User->>Client: Click "Login"
Client-->>User: 302 Redirect to Auth (with PKCE Challenge)
User->>Auth: Enter Credentials
Auth-->>User: 302 Redirect to Callback (with Code)
User->>Client: GET /callback?code=...
Client->>Auth: POST /token (Code + PKCE Verifier)
Auth-->>Client: JSON { access_token, refresh_token }
Note right of Client: Client is now authenticated
from urllib.parse import urlencode
import secrets
def generate_authorization_url(client_id: str, redirect_uri: str) -> str:
state = secrets.token_urlsafe(32)
code_verifier = secrets.token_urlsafe(64)
# PKCE: Generate code challenge
import hashlib, base64
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).decode().rstrip('=')
params = {
'response_type': 'code',
'client_id': client_id,
'redirect_uri': redirect_uri,
'scope': 'openid profile email',
'state': state,
'code_challenge': code_challenge,
'code_challenge_method': 'S256',
}
return f"https://auth.example.com/authorize?{urlencode(params)}"import httpx
async def exchange_code_for_tokens(
code: str,
client_id: str,
client_secret: str,
redirect_uri: str,
code_verifier: str,
) -> dict:
async with httpx.AsyncClient() as client:
response = await client.post(
"https://auth.example.com/token",
data={
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': redirect_uri,
'client_id': client_id,
'client_secret': client_secret,
'code_verifier': code_verifier,
},
)
return response.json()Access tokens are short-lived. To stay logged in, we exchange the refresh token for a new pair of tokens:
async def refresh_access_token(
refresh_token: str,
client_id: str,
client_secret: str,
) -> dict:
async with httpx.AsyncClient() as client:
response = await client.post(
"https://auth.example.com/token",
data={
'grant_type': 'refresh_token',
'refresh_token': refresh_token,
'client_id': client_id,
'client_secret': client_secret,
},
)
return response.json()Now we need to protect our API routes. Here is a simple middleware example using FastAPI:
from fastapi import Request, HTTPException
async def verify_token(request: Request):
auth_header = request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '):
raise HTTPException(status_code=401, detail="Missing token")
token = auth_header.split(' ')[1]
# In production, verify JWT signature here!
user_info = await fetch_user_info(token)
request.state.user = user_infoWhen implementing OAuth2, these are the critical security measures:
Pro Tip: Store refresh tokens encrypted at rest, and implement token binding to prevent token theft from being useful.
Understanding OAuth2 internals makes you a better engineer, even if you never implement it from scratch in production. The security patterns—PKCE, state validation, token rotation—apply broadly across authentication systems.
Core Engineering Group
Building robust, scalable applications with modern best practices.
Understanding the fundamental differences between HTTP/1.1 and HTTP/2 with visual diagrams and practical examples
A deep dive into how PostgreSQL's query planner works, from parsing SQL to generating optimal execution plans. Understand EXPLAIN output like never before.
How to run schema migrations on billion-row tables without any downtime. Covers expand-contract pattern, online DDL tools, and rollback strategies.