Authorization Code Grant — The Most Secure OAuth2 Flow for Web Apps
In this tutorial, you will learn about Authorization Code Grant. We cover key concepts, practical examples, and best practices to help you master this topic.
The Authorization Code grant is the most secure OAuth2 flow, where the client receives a temporary code after user authorization and exchanges it for tokens on the server side.
What You'll Learn
How the Authorization Code flow works, why it is the most secure grant, how the state parameter prevents CSRF, and implementation with a web application.
Why It Matters
The Authorization Code grant keeps user credentials between the user and the authorization server. The client never sees the password. Even the authorization code has a short lifetime and requires a client secret to exchange, making interception useless.
Real-World Use
"Sign in with Google" on any website uses the Authorization Code grant. GitHub OAuth, Facebook Login, and most social login implementations use this flow.
sequenceDiagram
participant User
participant Client as Web App
participant Auth as Auth Server
participant API as Resource Server
User->>Client: Click "Login with Google"
Client->>Auth: Redirect to /auth?response_type=code
User->>Auth: Authenticate, approve scopes
Auth->>Client: Redirect to /callback?code=A1b2C3
Client->>Auth: POST /token & code + secret
Auth->>Client: Access Token + Refresh Token
Client->>API: GET /data (Bearer Token)
API->>Client: Protected Data
Client->>User: Show Page
Code Example: Authorization Code Flow (Callback Handler)
from flask import Flask, request, redirect, jsonify
import requests, secrets
app = Flask(__name__)
CLIENT_ID = "your-client-id"
CLIENT_SECRET = "your-client-secret"
REDIRECT_URI = "https://yourapp.com/callback"
AUTH_URL = "https://auth.example.com/oauth/authorize"
TOKEN_URL = "https://auth.example.com/oauth/token"
@app.route("/login")
def login():
state = secrets.token_urlsafe(32)
# Store state in session for verification
params = {
"response_type": "code",
"client_id": CLIENT_ID,
"redirect_uri": REDIRECT_URI,
"scope": "openid email profile",
"state": state
}
auth_url = f"{AUTH_URL}?{requests.compat.urlencode(params)}"
return redirect(auth_url)
@app.route("/callback")
def callback():
# Verify state to prevent CSRF
state = request.args.get("state")
code = request.args.get("code")
if not code or not state:
return "Missing parameters", 400
# Exchange code for tokens
token_response = requests.post(TOKEN_URL, data={
"grant_type": "authorization_code",
"code": code,
"redirect_uri": REDIRECT_URI,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET
})
if token_response.status_code != 200:
return "Token exchange failed", 400
tokens = token_response.json()
access_token = tokens["access_token"]
return jsonify({
"message": "Authenticated!",
"token_preview": access_token[:20] + "..."
})
if __name__ == "__main__":
app.run()
Why the State Parameter Matters
Without state, an attacker can:
- Generate their own authorization URL
- Trick the user into authorizing
- Intercept the redirect and swap the authorization code
The state parameter is a unique, unguessable value generated by the client. When the redirect returns, the client verifies the state matches what it sent, proving the flow was not tampered with.
Common Mistakes
1. Missing State Parameter
Without state, CSRF Attacks can swap the authorization code. Always generate a unique, unguessable state and verify it in the callback.
2. Using Authorization Code Without PKCE on Mobile
Mobile apps cannot keep client secrets. Use PKCE to add cryptographic proof that the same client that started the flow is completing it.
3. Long Authorization Code Lifetime
Authorization codes should expire within 1-2 minutes. A code intercepted after the user walks away from their computer is no longer valid.
4. Not Validating redirect_uri
The authorization server must validate the redirect_uri against the registered URI. Otherwise, an attacker can use open redirectors to intercept codes.
5. Exposing Client Secret in Frontend
Client secrets are for confidential clients (backend). Public clients (SPA, mobile) must use PKCE without a secret.
Practice Questions
- Why is Authorization Code the most secure OAuth2 grant?
- What is the purpose of the authorization code?
- How does the state parameter prevent CSRF?
- Why must authorization codes expire quickly?
- What happens during the code exchange step?
Answers:
- User credentials stay between the user and the auth server. The client never sees the password. The authorization code is temporary and requires a client secret to exchange.
- The authorization code is a temporary credential (1-2 minute lifetime) that the client exchanges for access and refresh tokens. It is useless if intercepted.
- The client generates a unique state value, sends it with the auth request, and verifies it in the callback. An attacker cannot guess the state and therefore cannot forge a callback.
- If the code is intercepted (open redirect, compromised network), the short lifetime prevents the attacker from exchanging it for tokens.
- The client sends the code, client_id, client_secret, and redirect_uri to the token endpoint. The auth server validates these and returns access and refresh tokens.
Challenge: Implement a complete Authorization Code flow with state parameter verification, short-lived code enforcement, and secure token storage on the server.
FAQ
Mini Project
Build a Flask web app implementing the Authorization Code flow: login redirect to auth server, callback handler with state verification, code exchange, and token storage in session.
What's Next
Now learn about Proof Key for Code Exchange (PKCE) which secures the Authorization Code flow for mobile apps and single-page applications.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro