Skip to content

OIDC Flows — Authorization Code, Implicit, and Hybrid Authentication Flows

DodaTech Updated 2026-06-28 5 min read

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

  1. What is the main advantage of authorization code flow over implicit flow?
  2. Why do mobile apps need PKCE when using authorization code flow?
  3. What happens if an authorization code is used twice?
  4. Why is implicit flow considered less secure?
  5. How does hybrid flow combine benefits of both approaches?

Answers:

  1. The code flow keeps tokens off the browser URL and authenticates the client with a secret, preventing token interception.
  2. Mobile apps cannot securely store a client secret. PKCE cryptographically binds the code to the app without a secret.
  3. The provider should reject repeated code usage. Legitimate users should re-authenticate.
  4. Implicit flow exposes tokens in the URL fragment, cannot use client authentication, and is vulnerable to access token leakage.
  5. 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

What is the `nonce` parameter used for in these flows?

: The nonce prevents replay attacks. It is included in the auth request and verified in the ID token to ensure the token was issued for this specific request.

Can I use refresh tokens with all flows?

: Yes, if the provider supports the refresh_token grant type. The authorization code flow always includes a refresh token. Implicit flow does not.

How does the provider know which client type is requesting?

: The provider does not know. The client chooses the response type. The developer must pick the appropriate flow.

What is the `code_challenge_method` parameter?

: It specifies how the PKCE challenge was created. S256 (SHA-256) is secure. plain is not recommended.

Is hybrid flow more secure than authorization code flow?

: No. Both are secure. Hybrid flow offers faster user identification but adds complexity. Code flow is simpler and equally secure.

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