OIDC Security — Protecting Against Replay Attacks, CSRF, and Token Theft
In this tutorial, you will learn about OIDC Security. We cover key concepts, practical examples, and best practices to help you master this topic.
OpenID Connect security relies on several mechanisms to protect against common attacks: the nonce parameter prevents token replay, the state parameter prevents CSRF, and proper token storage prevents credential theft.
What You'll Learn
- How the
nonceparameter prevents ID token replay attacks - How the
stateparameter prevents CSRF Attacks - Best practices for secure token storage and transmission
Why It Matters
An attacker who intercepts an ID token can replay it to impersonate a user. A CSRF attack can trick a user into linking their account to an attacker's session. Without proper OIDC security measures, your authentication system is vulnerable to these and other attacks.
Real-World Use
Doda Browser's OIDC implementation uses a cryptographically random nonce embedded in every authentication request. The backend verifies the nonce in the returned ID token, ensuring that even if a token is intercepted, it cannot be replayed. The state parameter is bound to the user's session, preventing CSRF attacks.
flowchart LR
A["User"] -->|"Auth Request\n+ state + nonce"| B["OIDC Provider"]
B -->|"ID Token\n+ state + nonce"| C["Your App"]
C --> D{"Verify state\nmatches session?"}
D -->|No| E["Reject\nCSRF detected"]
D -->|Yes| F{"Verify nonce\nmatches request?"}
F -->|No| G["Reject\nReplay detected"]
F -->|Yes| H["Accept Token"]
style E fill:#fecaca,stroke:#dc2626
style G fill:#fecaca,stroke:#dc2626
style H fill:#bbf7d0,stroke:#16a34a
Nonce-Based Replay Prevention
The nonce is a random value sent with the authentication request that is embedded in the resulting ID token:
import secrets
import jwt
# Generate a nonce for each authentication request
def create_auth_request(provider_config):
nonce = secrets.token_urlsafe(32)
state = secrets.token_urlsafe(16)
# Store nonce and state in session
session['oidc_nonce'] = nonce
session['oidc_state'] = state
params = {
'client_id': provider_config['client_id'],
'redirect_uri': provider_config['redirect_uri'],
'response_type': 'code',
'scope': 'openid profile email',
'state': state,
'nonce': nonce
}
return params
# Verify nonce in the returned ID token
def verify_id_token(encoded_token, expected_nonce, jwks, expected_issuer, expected_audience):
payload = jwt.decode(
encoded_token,
jwks,
algorithms=["RS256"],
issuer=expected_issuer,
audience=expected_audience
)
received_nonce = payload.get('nonce')
if received_nonce != expected_nonce:
raise ValueError("Nonce mismatch - possible replay attack")
return payload
State Parameter CSRF Protection
The state parameter links the authorization response to the original request:
// Express middleware for OIDC state validation
function validateState(req, res, next) {
const returnedState = req.query.state;
const storedState = req.session.oidcState;
if (!returnedState || returnedState !== storedState) {
// CSRF attack detected
return res.status(401).json({
error: 'invalid_state',
message: 'State parameter mismatch. Possible CSRF attack.'
});
}
// Clear used state to prevent reuse
delete req.session.oidcState;
next();
}
Token Binding and Storage
Secure token storage is critical for preventing theft:
# Server-side: store tokens in HTTP-only, secure cookies
def set_token_cookies(response, id_token, access_token, refresh_token):
response.set_cookie(
'id_token',
id_token,
httponly=True,
secure=True,
samesite='Lax',
max_age=3600,
path='/'
)
response.set_cookie(
'access_token',
access_token,
httponly=True,
secure=True,
samesite='Lax',
max_age=3600,
path='/'
)
# Refresh token: shorter lifetime, strict path
if refresh_token:
response.set_cookie(
'refresh_token',
refresh_token,
httponly=True,
secure=True,
samesite='Strict',
max_age=86400,
path='/auth/refresh'
)
return response
Common Mistakes
1. Reusing Nonces
If the same nonce is used across multiple requests, a captured token can be replayed until the nonce changes. Generate a fresh nonce for every authentication request.
2. Not Checking the State Parameter
Without state validation, an attacker can craft a URL that authenticates the victim and redirects them to the attacker's session. Always validate state.
3. Storing Tokens in localStorage
localStorage is accessible to any JavaScript on the same origin. XSS Attacks can steal tokens. Use HTTP-only cookies instead.
4. Not Rotating Signing Keys
OIDC providers should rotate their JWKS signing keys periodically. Applications should handle key rotation gracefully by fetching fresh JWKS on key errors.
5. Accepting Tokens Over HTTP
ID tokens and access tokens must never be transmitted over unencrypted connections. Enforce HTTPS at every level.
Practice Questions
- What attack does the nonce parameter prevent?
- What attack does the state parameter prevent?
- Why should tokens be stored in HTTP-only cookies instead of localStorage?
- How often should signing keys be rotated?
- What should you do if ID token validation fails due to an unknown key?
Answers
- ID token replay attacks. 2. CSRF (Cross-Site Request Forgery). 3. HTTP-only cookies are not accessible to JavaScript, preventing XSS-based token theft. 4. Periodically, with a grace period for old keys. 5. Fetch the latest JWKS and retry validation.
Challenge
Build a complete OIDC security test suite that validates an implementation against the most common attacks: nonce replay, CSRF via state manipulation, token interception via open redirect, and JWKS key confusion attacks. Each test should demonstrate the attack and verify the defense.
FAQ
Mini Project
Build a security audit tool that scans an OIDC implementation for common vulnerabilities: checks for nonce usage, state validation, cookie security flags, HTTPS enforcement, and JWKS rotation. Generate a report with severity levels and remediation steps.
What's Next
- Explore popular OIDC providers and their features
- Learn about OIDC testing strategies with mock providers
- Continue to building a complete OIDC project
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro