OIDC Response Types — How Tokens Are Delivered in Authentication Responses
In this tutorial, you will learn about OIDC Response Types. We cover key concepts, practical examples, and best practices to help you master this topic.
Response types in OIDC determine how authentication results are returned to the client, whether through an authorization code exchanged server-side, tokens delivered directly in the URL fragment, or a hybrid of both approaches.
What You'll Learn
- The three main OIDC response types: code, id_token, and token
- Hybrid response types combining multiple delivery methods
- Security considerations for each response type
Why It Matters
Choosing the wrong response type exposes tokens to the browser URL, enables interception attacks, or requires unnecessary server-side complexity. Each response type has specific security properties and use cases.
Real-World Use
Doda Browser uses response_type=code for all web logins. The authorization code is exchanged server-side for tokens, keeping ID tokens and access tokens out of the browser URL. For native mobile apps, it uses response_type=code with PKCE for additional security.
flowchart LR
subgraph "Authorization Code Flow"
Client1["Client"] -->|"code"| Provider["Provider"]
Provider -->|"code"| Client1
Client1 -->|"code + secret"| TokenEP["Token Endpoint"]
TokenEP -->|"ID + Access Token"| Client1
end
subgraph "Implicit Flow"
Client2["Client"] -->|"id_token + access_token"| Provider2["Provider"]
Provider2 -->|"fragment"| Client2
end
style TokenEP fill:#dbeafe,stroke:#2563eb
Authorization Code Flow (response_type=code)
The client receives an authorization code, which is exchanged server-side for tokens:
import requests
# Step 1: Redirect user to authorization endpoint
auth_params = {
"client_id": "my-app",
"response_type": "code",
"redirect_uri": "https://app.com/callback",
"scope": "openid profile email",
"state": "random-state",
}
# User redirected to provider...
# Step 2: Exchange code for tokens (server-side)
token_resp = requests.post("https://provider.com/token", data={
"code": "authorization-code-from-callback",
"client_id": "my-app",
"client_secret": "my-secret",
"redirect_uri": "https://app.com/callback",
"grant_type": "authorization_code",
})
tokens = token_resp.json()
print(f"ID Token: {tokens.get('id_token')[:50]}...")
print(f"Access Token: {tokens.get('access_token')[:50]}...")
Implicit Flow (response_type=id_token token)
Tokens are returned directly in the URL fragment. No server-side exchange needed:
Redirect URL:
https://app.com/callback#id_token=eyJhbGciOiJSUzI1...&access_token=ya29.a0AfH6S...&state=random-state&expires_in=3600&token_type=Bearer
The browser reads tokens from the fragment. This flow is less secure because tokens are exposed in the browser URL.
Hybrid Flow (response_type=code id_token)
Combines both approaches: the client receives an authorization code and an ID token simultaneously:
from flask import Flask, request
import requests
import jwt
app = Flask(__name__)
@app.route("/callback")
def callback():
# Hybrid flow returns both code and id_token
code = request.args.get("code")
id_token_from_fragment = request.args.get("id_token")
# Immediately verify ID token for user identity
claims = jwt.decode(id_token_from_fragment, options={"verify_signature": False})
print(f"User identified: {claims.get('name')}")
# Exchange code for additional tokens
tokens = requests.post("https://provider.com/token", data={
"code": code,
"client_id": "my-app",
"client_secret": "my-secret",
"redirect_uri": "https://app.com/callback",
"grant_type": "authorization_code",
}).json()
access_token = tokens["access_token"]
return f"Hello {claims['name']}! Access token: {access_token[:30]}..."
Response Type Comparison
| Type | Tokens Returned | Server Exchange Needed | Token Exposure |
|---|---|---|---|
code |
Authorization code | Yes | Minimal |
id_token |
ID token only | No | URL fragment |
token id_token |
ID + Access token | No | URL fragment |
code id_token |
Code + ID token | Partial | URL fragment |
code token |
Code + Access token | Partial | URL fragment |
code id_token token |
Code + ID + Access token | Partial | URL fragment |
PKCE with Authorization Code Flow
For public clients (SPA, mobile), add PKCE for protection:
import hashlib
import secrets
import base64
def generate_pkce_pair():
code_verifier = secrets.token_urlsafe(64)
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b"=").decode()
return code_verifier, code_challenge
# In auth request
code_verifier, code_challenge = generate_pkce_pair()
auth_params = {
"response_type": "code",
"code_challenge": code_challenge,
"code_challenge_method": "S256",
# ... other params
}
# In token exchange
token_resp = requests.post("https://provider.com/token", data={
"code": authorization_code,
"client_id": "my-app",
"code_verifier": code_verifier,
"grant_type": "authorization_code",
})
Common Mistakes
1. Using Implicit Flow in SPAs
Implicit flow exposes tokens in the URL. Use authorization code flow with PKCE for SPAs. Most providers have deprecated implicit flow.
2. Storing Tokens from URL Fragments in Logs
URL fragments may be logged by web servers, exposing tokens. Filter fragment data from server logs.
3. Not Validating at_hash in Hybrid Flow
The at_hash claim in the ID token binds it to the access token. Verify it matches to prevent token swapping.
4. Exposing Client Secret in Implicit Flow
Implicit flow cannot use client secrets because the secret would be exposed in the browser. Use PKCE instead.
5. Mixing Response Types Without Understanding
Not all providers support all hybrid types. Check response_types_supported in the discovery document.
Practice Questions
- What is the difference between
response_type=codeandresponse_type=id_token token? - Why is the authorization code flow more secure than implicit flow?
- What is PKCE and why is it important for public clients?
- What does hybrid flow (
code id_token) give you? - How does
at_hashprotect hybrid flow tokens?
Answers:
codereturns an authorization code exchanged server-side for tokens.id_token tokenreturns tokens directly in the URL fragment.- The code flow keeps tokens off the browser URL. Codes are single-use and exchanged server-side with a client secret.
- PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks by binding the code to a cryptographic verifier.
- Hybrid flow provides immediate user identity (ID token) while also delivering a code for server-side token exchange.
at_hashis a hash of the access token embedded in the ID token, proving they belong together and preventing token substitution.
Challenge: Implement both authorization code flow (with PKCE) and implicit flow for the same OIDC provider. Compare the security properties and explain why code + PKCE is recommended.
FAQ
Mini Project
Create three Python scripts demonstrating each OIDC flow: authorization code flow with PKCE, implicit flow (handling fragment tokens), and hybrid flow (processing both code and ID token). Document the security differences.
What's Next
Continue with OIDC Flows Deep Dive to explore the authorization code, implicit, and hybrid flows in detail, or work on the OIDC Implementation Project.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro