OIDC Flows — Authorization Code, Implicit, and Hybrid Authentication Flows
In this tutorial, you will learn about OIDC Flows. We cover key concepts, practical examples, and best practices to help you master this topic.
OIDC defines three authentication flows that determine how tokens are delivered to the client: the authorization code flow, the implicit flow, and the hybrid flow, each with different security properties and use cases.
What You'll Learn
- Step-by-step walkthrough of each OIDC flow
- When to use each flow based on client type
- Security considerations and PKCE extension
Why It Matters
Choosing the right flow affects both security and user experience. A mobile app using implicit flow exposes tokens to interception. A server-side web app using authorization code flow with client secret provides the strongest security.
Real-World Use
Doda Browser uses authorization code flow for its web app (server-side token exchange), authorization code with PKCE for its mobile app (no client secret), and never uses implicit flow for production.
sequenceDiagram
participant User
participant Client
participant Provider
participant TokenEP as Token Endpoint
Note over Client,TokenEP: Authorization Code Flow
User->>Client: Click Login
Client->>Provider: Auth Request (response_type=code)
Provider->>User: Login + Consent
User->>Provider: Credentials
Provider->>Client: Authorization Code
Client->>TokenEP: Code + Client Secret
TokenEP->>Client: ID Token + Access Token
Authorization Code Flow (Step by Step)
import requests
import secrets
from flask import Flask, request, session, redirect
app = Flask(__name__)
app.secret_key = secrets.token_urlsafe(32)
CLIENT_ID = "my-app"
CLIENT_SECRET = "my-secret"
REDIRECT_URI = "https://app.com/callback"
AUTH_ENDPOINT = "https://provider.com/auth"
TOKEN_ENDPOINT = "https://provider.com/token"
@app.route("/login")
def login():
state = secrets.token_urlsafe(32)
nonce = secrets.token_urlsafe(16)
session["oauth_state"] = state
session["oauth_nonce"] = nonce
params = {
"client_id": CLIENT_ID,
"response_type": "code",
"redirect_uri": REDIRECT_URI,
"scope": "openid profile email",
"state": state,
"nonce": nonce,
}
import urllib.parse
auth_url = f"{AUTH_ENDPOINT}?{urllib.parse.urlencode(params)}"
return redirect(auth_url)
@app.route("/callback")
def callback():
if request.args.get("state") != session.get("oauth_state"):
return "CSRF detected", 400
resp = requests.post(TOKEN_ENDPOINT, data={
"grant_type": "authorization_code",
"code": request.args["code"],
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"redirect_uri": REDIRECT_URI,
})
tokens = resp.json()
# Verify and decode ID token
id_token = tokens["id_token"]
# Verify signature, exp, aud, nonce here
user = jwt.decode(id_token, options={"verify_signature": False})
return f"Welcome, {user['name']}!"
Authorization Code with PKCE (Mobile/SPA)
import hashlib, base64
def generate_pkce():
verifier = secrets.token_urlsafe(64)
challenge = base64.urlsafe_b64encode(
hashlib.sha256(verifier.encode()).digest()
).rstrip(b"=").decode()
return verifier, challenge
@app.route("/login")
def login_pkce():
state = secrets.token_urlsafe(32)
verifier, challenge = generate_pkce()
session["pkce_verifier"] = verifier
params = {
"client_id": CLIENT_ID,
"response_type": "code",
"redirect_uri": REDIRECT_URI,
"scope": "openid profile email",
"state": state,
"code_challenge": challenge,
"code_challenge_method": "S256",
}
# Build and redirect...
Implicit Flow (Legacy)
@app.route("/login-implicit")
def login_implicit():
params = {
"client_id": CLIENT_ID,
"response_type": "id_token token",
"redirect_uri": REDIRECT_URI,
"scope": "openid profile email",
"state": secrets.token_urlsafe(32),
"nonce": secrets.token_urlsafe(16),
}
# Tokens returned in URL fragment
# http://app.com/callback#id_token=xxx&access_token=yyy
Flow Selection Guide
| Client Type | Recommended Flow | Why |
|---|---|---|
| Server-side web app | Authorization code | Secret kept server-side |
| Single-page app (SPA) | Authorization code + PKCE | No secret, PKCE protects |
| Mobile app | Authorization code + PKCE | No secret, PKCE protects |
| Native desktop | Authorization code + PKCE | No secret, PKCE protects |
| Legacy JS app | Implicit (deprecated) | Only if provider lacks PKCE |
Common Mistakes
1. Authorization Code Without PKCE in SPAs
Without PKCE, a malicious app can intercept the authorization code. PKCE binds the code to a specific client instance.
2. Storing Client Secret in Mobile Apps
Client secrets extracted from mobile apps through decompilation. Mobile apps should never use secrets.
3. Not Validating Redirect URI Match
If the provider allows any redirect URI, attackers can steal codes. Register exact URIs.
4. Single-Use Code Reuse
Authorization codes are single-use. Reusing a code should be detected and rejected by the provider.
5. Ignoring Provider Flow Restrictions
Some providers restrict certain flows or require PKCE. Check the discovery document.
Practice Questions
- What is the main advantage of authorization code flow over implicit flow?
- Why do mobile apps need PKCE when using authorization code flow?
- What happens if an authorization code is used twice?
- Why is implicit flow considered less secure?
- How does hybrid flow combine benefits of both approaches?
Answers:
- The code flow keeps tokens off the browser URL and authenticates the client with a secret, preventing token interception.
- Mobile apps cannot securely store a client secret. PKCE cryptographically binds the code to the app without a secret.
- The provider should reject repeated code usage. Legitimate users should re-authenticate.
- Implicit flow exposes tokens in the URL fragment, cannot use client authentication, and is vulnerable to access token leakage.
- Hybrid flow provides immediate user identity from the ID token while using the code for server-side token exchange with client authentication.
Challenge: Implement all three OIDC flows with a test provider. For each flow, capture and compare the tokens returned, the delivery method, and the security properties.
FAQ
Mini Project
Build a Python Flask application that supports all three OIDC flows with configurable provider settings. Include a flow selector page where users can choose the flow and see the security implications of each choice.
What's Next
Continue with the OIDC Implementation Project to build a complete OIDC login system, or review OpenID Connect Introduction for a refresher on core concepts.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro