State links an OAuth response to the request that started it, which stops CSRF. Nonce ties an OpenID Connect ID token to one login attempt, which stops replay. PKCE ties an authorization code to the client that asked for it, which stops code injection and theft. They overlap, but they are not the same.
The three values
| Value | Sent in | Comes back in | Main job |
|---|---|---|---|
| state | Authorization request | Redirect to your app | Bind the response to your browser session (CSRF) |
| nonce | OIDC authorization request | ID token claim | Bind the ID token to one session (replay) |
| code_verifier and code_challenge | Challenge in the request, verifier at token endpoint | Not returned | Bind the code to the client that requested it |
State
The long established CSRF countermeasure, per RFC 9700, is for the client to pass a random value in the state parameter that links the request to the redirect and to the user agent session. When you check it on return, you reject responses your browser never asked for.
Two details from the RFC. State must be one time use and securely bound to the user agent. And if you also use state to carry app data, protect it against tampering and swapping.
Nonce
The OpenID Connect spec describes the nonce as a string value that associates a client session with an ID token and mitigates replay attacks. You send it in the request, the server copies it into the ID token, and your client must verify that the claim equals what it sent. The spec says it needs enough entropy that attackers cannot guess it.
PKCE
PKCE (RFC 7636) protects against authorization code injection. RFC 9700 explains the idea: when an attacker injects a code, the check of the verifier fails, because the code is tied to a challenge that does not match the client's verifier. PKCE also means a stolen code cannot be redeemed at the token endpoint without the verifier.
Which do you need
RFC 9700 says:
- Public clients must use PKCE. For confidential clients it is recommended.
- PKCE also gives CSRF protection, but only if the authorization server supports it. If it does not, you must use state or nonce.
- In OpenID Connect flows, nonce can provide CSRF protection.
- Otherwise, one time CSRF tokens in state, bound to the user agent, are required.
A safe default for a new app: use PKCE, and still send state and nonce. They are cheap, and defence in depth helps when one piece is misconfigured.
Mistakes to look for in review
- A static or guessable state value.
- State generated but never checked on return.
- Nonce checked for presence but not for equality.
- PKCE with the
plainmethod whenS256is available. RFC 7636 says a client that can use S256 must use it. - Reusing the same verifier across requests.
Try it yourself
Click through it at your own pace, or open it full screen. Look at how code findings point to a file and line, the same way a review would flag a state value that is never checked.
Quick check
If I use PKCE, can I drop state?
RFC 9700 allows PKCE to replace state for CSRF protection only if you have confirmed the authorization server supports it. Many teams keep state anyway.
Does nonce apply to plain OAuth without OpenID Connect?
No. Nonce is an OpenID Connect parameter tied to the ID token. Plain OAuth relies on state and PKCE.
Safeguard's SBOM and dependency scanning shows which OAuth client libraries you ship, which helps when a library advisory affects how these values are handled. Try a free scan.
Sources
- RFC 9700, Best Current Practice for OAuth 2.0 Security
- RFC 7636, Proof Key for Code Exchange
- OpenID Connect Core 1.0
Last reviewed: 19 October 2026.