Redirect URIs and the state parameter
Two checks that stop an attacker stealing the authorisation code.
Open this lesson in the learning hubKey points
- The redirect URI is where the authorisation code is delivered. If an attacker can influence it, they receive the code and the whole flow is compromised.
- Providers therefore match it exactly against a pre-registered list - full string, including scheme, host, port and path. That strictness is the security control, not an inconvenience.
- Never allow wildcards or open redirects in that path. An open redirect anywhere on a registered host effectively hands over the code.
- The state parameter is CSRF protection for the flow. The client generates a random value, sends it, and rejects the callback if it does not match what it stored.
- Without state, an attacker can start their own authorisation and trick a victim into completing it, linking the victim session to the attacker account.
- In OIDC, nonce does the equivalent job for the ID token: it binds the token to this specific authentication request and prevents replay.
Example
# The authorisation request
GET https://auth.example.com/authorize
?response_type=code
&client_id=my-app
&redirect_uri=https://app.example.com/callback # EXACT match required
&scope=openid%20profile
&state=xyz123-random-per-request # CSRF protection
&nonce=abc789-random-per-request # OIDC replay protection
&code_challenge=E9Melhoa...&code_challenge_method=S256
# The callback
GET https://app.example.com/callback?code=SplxlOB&state=xyz123
# ^^^^^^^^
# Compare against what YOU stored in the session. Not equal -> reject,
# and do not exchange the code.
---
# REDIRECT URI MATCHING - exact, not prefix:
#
# registered: https://app.example.com/callback
#
# https://app.example.com/callback MATCH
# https://app.example.com/callback/ no
# https://app.example.com/callback?x=1 no (extra query)
# http://app.example.com/callback no (scheme)
# https://app.example.com:8443/callback no (port)
#
# Anything looser is exploitable.
---
# THE ATTACK state PREVENTS:
#
# 1. attacker begins a login and gets their own code
# 2. attacker sends the victim: https://app.example.com/callback?code=ATTACKER
# 3. without a state check, the app exchanges it and links the victim
# browser session to the ATTACKER account
# 4. the victim then enters data into an account the attacker controls
#
# Spring Security generates and validates state and nonce for you. This is
# a strong argument against hand-rolling the flow.
Redirect URIs must match exactly and state must be checked on the callback - both protect the authorisation code itself.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the OAuth 2.0 Course course, and every lesson in it is listed on the OAuth 2.0 Course contents page.