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
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
requestsandflaskinstalled (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
- Log in to the provider’s developer console.
- Create a new OAuth client.
- Note the client ID and client secret.
- Set the redirect URI to the endpoint that will handle the callback (e.g.,
http://localhost:5000/callback). - Optionally enable the Authorization Code grant type and any required scopes.
2. Build the Authorization URL
The user is sent to an endpoint like:
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_STRINGresponse_type=codetells the provider we want an authorization code.statemitigates CSRF; generate a random string and store it for later verification.
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:
GET /callback?code=AUTH_CODE&state=RANDOM_STRING4. Verify the State Parameter
Before exchanging the code, confirm the state value matches the one you generated. This prevents cross‑site request forgery.
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:
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_SECRETimport 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_type6. Use the Access Token
Include the token in the Authorization header when calling a protected API:
GET https://api.example.com/user
Authorization: Bearer ACCESS_TOKENdef 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.
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
stateparameter 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.
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
- /login – Generates a random
state, stores it in the session, builds the authorization URL, and redirects the user. - /callback – Receives the
codeandstate, validates the state, exchanges the code for tokens, and saves theaccess_tokenin the session. - /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.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.