Skip to content

Authorization Code Grant — The Most Secure OAuth2 Flow for Web Apps

DodaTech Updated 2026-06-28 4 min read

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:

  1. Generate their own authorization URL
  2. Trick the user into authorizing
  3. 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

  1. Why is Authorization Code the most secure OAuth2 grant?
  2. What is the purpose of the authorization code?
  3. How does the state parameter prevent CSRF?
  4. Why must authorization codes expire quickly?
  5. What happens during the code exchange step?

Answers:

  1. 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.
  2. 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.
  3. 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.
  4. If the code is intercepted (open redirect, compromised network), the short lifetime prevents the attacker from exchanging it for tokens.
  5. 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

Is Authorization Code the same as OAuth2?

Authorization Code is one grant type within OAuth2. It is the most common and most secure grant for web applications.

Can Authorization Code be used without a client secret?

For public clients (mobile, SPA), yes — use Authorization Code with PKCE. PKCE adds a code verifier that replaces the client secret.

How long should the state parameter be?

At least 128 bits (32 bytes) of entropy from a cryptographically secure random generator. secrets.token_urlsafe(32) works well.

What if the user denies authorization?

The authorization server redirects back to the redirect_uri with an error parameter (access_denied). The client should handle this gracefully.

Does Authorization Code support MFA?

Yes. Since the user authenticates directly with the authorization server (not the client), MFA challenges work naturally.

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