Redirect URIs and the state parameter

OAuth 2.0 Course · lesson 10 of 13 · 6 min read

Two checks that stop an attacker stealing the authorisation code.

Open this lesson in the learning hub

Key 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.