The Role of the State Parameter
The state parameter is an opaque value the client generates before redirecting the user to the authorization endpoint, and the authorization server is required to return it unchanged when redirecting back to the client's callback. The client then checks that the returned state matches the value it originally issued for that specific browser session. This round trip proves the callback being processed corresponds to an authorization flow the current user's browser actually initiated, rather than one an attacker started and is now trying to complete inside the victim's session.
Cricket analogy: It's like a coin toss witnessed and recorded by both captains before the match, so neither side can later dispute which team chose to bat; state lets the client later verify this exact callback matches the flow it started.
Cryptographic Requirements for a Safe State Value
For state to actually stop CSRF, it must be unpredictable and bound to the specific user's session, not just any random-looking string. A common mistake is generating state with a weak or predictable random number generator, or storing it in a way that's shared across users, such as a global variable on the server rather than per-session storage. Best practice is to generate at least 128 bits of entropy using a cryptographically secure random number generator, store it server-side or in a signed, httpOnly cookie tied to the session, and reject the callback outright if the returned value doesn't match exactly.
Cricket analogy: A scorer using a truly random, uncrackable code to seal match predictions before the toss, rather than a guessable sequential number, mirrors why state needs cryptographic-grade randomness rather than predictable values.
State vs. PKCE: Complementary, Not Redundant
State and PKCE solve different problems and both are needed. State prevents CSRF against the authorization endpoint by ensuring the callback belongs to a flow the current browser session actually started. PKCE prevents authorization code interception by ensuring the party redeeming the code is the same party that initiated the request, regardless of which session started it. A flow can be CSRF-safe with valid state but still vulnerable to code interception without PKCE, and vice versa, which is why modern guidance requires implementing both together rather than treating either as a full substitute for the other.
Cricket analogy: Checking both the bowler's front-foot no-ball line and the wicketkeeper's stumping timing are two separate umpiring checks that catch different infractions, just as state and PKCE catch different classes of attack.
import secrets
from flask import session, redirect, request, abort
@app.route("/login")
def login():
state = secrets.token_urlsafe(32) # >=128 bits of entropy
session["oauth_state"] = state
authorize_url = (
"https://authz.example.com/authorize"
f"?response_type=code&client_id={CLIENT_ID}"
f"&redirect_uri={CALLBACK_URL}&scope=openid profile"
f"&state={state}"
f"&code_challenge={code_challenge}&code_challenge_method=S256"
)
return redirect(authorize_url)
@app.route("/callback")
def callback():
returned_state = request.args.get("state")
expected_state = session.pop("oauth_state", None)
if not expected_state or not secrets.compare_digest(returned_state or "", expected_state):
abort(400, "Invalid or missing state parameter")
# proceed to exchange the authorization code for tokens
Validate state with a constant-time comparison and always pull the expected value from server-side or signed session storage, never from a client-supplied cookie the attacker could also set.
- State ties the authorization callback back to the exact browser session that initiated the flow.
- Without state validation, an attacker can force login CSRF by completing the flow using the victim's browser.
- State must be generated with a cryptographically secure random number generator, at least 128 bits of entropy.
- State should be stored server-side or in a signed, httpOnly cookie, never in a globally shared variable.
- State and PKCE are complementary: state stops CSRF, PKCE stops code interception, and both are required.
- Comparison of returned state to expected state must be exact and ideally constant-time.
Practice what you learned
1. What attack does the state parameter primarily defend against?
2. What entropy requirement should the state value meet?
3. Why are state and PKCE both required rather than either alone?
4. Where should the expected state value be stored for comparison at the callback?
Was this page helpful?
You May Also Like
Common OAuth Vulnerabilities
A survey of the recurring implementation mistakes that turn OAuth 2.0 deployments into attack surfaces, from code interception to login CSRF.
PKCE in Depth
How Proof Key for Code Exchange binds an authorization request to its token exchange and why S256 is mandatory in modern OAuth deployments.
Redirect URI Validation
Why OAuth authorization servers must enforce exact, literal redirect_uri matching, and how open redirects and loose host checks turn into full code exfiltration.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics