Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes
Sponsored Reserved space — layout preview until AdSense is connected

OAuth 2.0 Authorization Code Flow: Step‑by‑Step Python Tutorial

A step‑by‑step guide to implementing the OAuth 2.0 Authorization Code Flow in Python, covering registration, building the auth URL, state verification, token exchange, and API usage with a Flask example.

Focus: OAuth 2.0 Authorization Code Flow

Sponsored

Sponsored Reserved space — layout preview until AdSense is connected

OAuth 2.0 Authorization Code Flow – Step‑by‑Step Tutorial

Overview

OAuth 2.0 is the de‑facto standard for delegated authorization. The Authorization Code Flow is designed for web‑server applications that can securely store a client secret. It separates the user’s credentials from the client, allowing the app to obtain an access token after the user authorizes it via a separate login page.

Prerequisites

  • Basic understanding of HTTP requests and responses.
  • A Python environment with requests and flask installed (pip install requests flask).
  • A registered OAuth client on a provider (e.g., Google, GitHub). You’ll receive a client ID, client secret, and a redirect URI where the provider will send the authorization code.

1. Register the Application

  1. Log in to the provider’s developer console.
  2. Create a new OAuth client.
  3. Note the client ID and client secret.
  4. Set the redirect URI to the endpoint that will handle the callback (e.g., http://localhost:5000/callback).
  5. Optionally enable the Authorization Code grant type and any required scopes.

2. Build the Authorization URL

The user is sent to an endpoint like:

Code snippet
https://auth_provider.com/oauth/authorize?
    response_type=code&
    client_id=YOUR_CLIENT_ID&
    redirect_uri=YOUR_ENCODED_REDIRECT_URI&
    scope=openid%20profile%20email&
    state=RANDOM_STRING
  • response_type=code tells the provider we want an authorization code.
  • state mitigates CSRF; generate a random string and store it for later verification.
Code snippet
import secrets
import urllib.parse

def build_auth_url(client_id, redirect_uri, scope, state):
    base = "https://auth_provider.com/oauth/authorize"
    params = {
        "response_type": "code",
        "client_id": client_id,
        "redirect_uri": redirect_uri,
        "scope": scope,
        "state": state,
    }
    return f"{base}?{urllib.parse.urlencode(params)}"

# Example usage
client_id = "your_client_id"
redirect_uri = "http://localhost:5000/callback"
scope = "openid profile email"
state = secrets.token_urlsafe(16)

auth_url = build_auth_url(client_id, redirect_uri, scope, state)
print("Visit this URL to authorize:")
print(auth_url)

3. User Authentication & Consent

When the user clicks the URL, the provider shows a login page (if needed) and then a consent screen listing the requested scopes. After approval, the provider redirects the browser to the redirect_uri with query parameters:

Code snippet
GET /callback?code=AUTH_CODE&state=RANDOM_STRING

4. Verify the State Parameter

Before exchanging the code, confirm the state value matches the one you generated. This prevents cross‑site request forgery.

Code snippet
def verify_state(stored_state, returned_state):
return secrets.compare_digest(stored_state, returned_state)

5. Exchange the Authorization Code for Tokens

Make a POST request to the token endpoint:

Code snippet
POST https://auth_provider.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=AUTH_CODE&
redirect_uri=YOUR_REDIRECT_URI&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET
Code snippet
import requests

def exchange_code_for_tokens(client_id, client_secret, redirect_uri, auth_code):
    token_url = "https://auth_provider.com/oauth/token"
    data = {
        "grant_type": "authorization_code",
        "code": auth_code,
        "redirect_uri": redirect_uri,
        "client_id": client_id,
        "client_secret": client_secret,
    }
    response = requests.post(token_url, data=data)
    response.raise_for_status()
    return response.json()  # contains access_token, refresh_token, expires_in, token_type

6. Use the Access Token

Include the token in the Authorization header when calling a protected API:

Code snippet
GET https://api.example.com/user
Authorization: Bearer ACCESS_TOKEN
Code snippet
def call_protected_api(access_token):
    api_url = "https://api.example.com/user"
    headers = {"Authorization": f"Bearer {access_token}"}
    response = requests.get(api_url, headers=headers)
    response.raise_for_status()
    return response.json()

7. Refresh the Access Token (Optional)

When the access token expires, use the refresh token to obtain a new one without user interaction.

Code snippet
def refresh_access_token(client_id, client_secret, refresh_token):
    token_url = "https://auth_provider.com/oauth/token"
    data = {
        "grant_type": "refresh_token",
        "refresh_token": refresh_token,
        "client_id": client_id,
        "client_secret": client_secret,
    }
    response = requests.post(token_url, data=data)
    response.raise_for_status()
    return response.json()

8. Security Considerations

  • HTTPS everywhere: never transmit client secret or tokens over plain HTTP.
  • Store secrets securely (environment variables, secret managers).
  • Validate the state parameter on every callback.
  • Prefer PKCE (Proof Key for Code Exchange) for public clients that cannot keep a secret.
  • Limit scopes to the minimum needed.
  • Handle token expiration gracefully; use refresh tokens when available.

Real‑World Example: Flask Web App

Below is a minimal Flask app that demonstrates the entire flow. It assumes you have the client credentials and redirect URI configured.

Code snippet
import os
import secrets
import urllib.parse
from flask import Flask, redirect, request, session, url_for, jsonify
import requests

app = Flask(__name__)
app.secret_key = os.urandom(24)  # needed for session storage

# Load configuration (in practice use env vars or a config file)
CLIENT_ID = os.getenv("CLIENT_ID")
CLIENT_SECRET = os.getenv("CLIENT_SECRET")
REDIRECT_URI = os.getenv("REDIRECT_URI")  # e.g., http://localhost:5000/callback
AUTH_URL = "https://auth_provider.com/oauth/authorize"
TOKEN_URL = "https://auth_provider.com/oauth/token"
API_URL = "https://api.example.com/user"

# Step 1: Login route – redirects user to provider
@app.route("/login")
def login():
    state = secrets.token_urlsafe(16)
    session["oauth_state"] = state
    params = {
        "response_type": "code",
        "client_id": CLIENT_ID,
        "redirect_uri": REDIRECT_URI,
        "scope": "openid profile email",
        "state": state,
    }
    auth_url = f"{AUTH_URL}?{urllib.parse.urlencode(params)}"
    return redirect(auth_url)

# Step 2: Callback route – handles the redirect
@app.route("/callback")
def callback():
    error = request.args.get("error")
    if error:
        return f"Error: {error}", 400

    code = request.args.get("code")
    returned_state = request.args.get("state")
    stored_state = session.pop("oauth_state", None)

    if not returned_state or not stored_state or not verify_state(stored_state, returned_state):
        return "Invalid state parameter", 400

    token_resp = exchange_code_for_tokens(
        CLIENT_ID, CLIENT_SECRET, REDIRECT_URI, code
    )
    access_token = token_resp["access_token"]
    # Store token in session (for demo only; use a DB in production)
    session["access_token"] = access_token
    return redirect(url_for("profile"))

# Step 3: Protected route – calls the API
@app.route("/profile")
def profile():
    access_token = session.get("access_token")
    if not access_token:
        return redirect(url_for("login"))
    data = call_protected_api(access_token)
    return jsonify(data)

# Helper functions (verify_state, exchange_code_for_tokens, call_protected_api) are defined earlier

if __name__ == "__main__":
    # Ensure required env vars are set
    required = ["CLIENT_ID", "CLIENT_SECRET", "REDIRECT_URI"]
    missing = [var for var in required if not os.getenv(var)]
    if missing:
        raise RuntimeError(f"Missing environment variables: {', '.join(missing)}")
    app.run(port=5000, debug=True)

How the Flask App Works

  1. /login – Generates a random state, stores it in the session, builds the authorization URL, and redirects the user.
  2. /callback – Receives the code and state, validates the state, exchanges the code for tokens, and saves the access_token in the session.
  3. /profile – Retrieves the token from the session, calls the protected API, and returns JSON data.

Run the app, navigate to http://localhost:5000/login, complete the provider’s login/consent steps, and you’ll see the API response displayed on /profile.

Summary

The OAuth 2.0 Authorization Code Flow consists of five core steps: (1) register the client, (2) direct the user to the authorization endpoint, (3) obtain the authorization code after user consent, (4) exchange that code for access (and optionally refresh) tokens, and (5) use the token to call protected resources. By following the step‑by‑step guide and the provided Flask example, junior developers can implement a secure, production‑ready OAuth integration in Python web applications.

Sponsored

Sponsored Reserved space — layout preview until AdSense is connected

Sponsored

Sponsored Reserved space — layout preview until AdSense is connected

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.