ASVS 5.0 V10.1.2 requires the OAuth client to accept values from the authorization server, such as an authorization code or ID token, only when they result from a flow started by the same user agent session and transaction. The client secrets involved (code_verifier, state, nonce) must be unguessable, specific to the transaction and bound to the client and the session. It is a Level 2 requirement.
Why it matters
Without that binding, an attacker can start a flow, get a code for their own account and trick a victim's browser into completing it. The victim ends up logged in as the attacker, or the attacker's code gets attached to the victim's session.
How to implement it
Generate fresh random values per login attempt, store them server side in the session, and check them on return.
import { randomBytes, createHash } from "node:crypto";
const b64url = (b: Buffer) => b.toString("base64url");
app.get("/login", (req, res) => {
const state = b64url(randomBytes(32));
const nonce = b64url(randomBytes(32));
const verifier = b64url(randomBytes(32));
const challenge = b64url(createHash("sha256").update(verifier).digest());
req.session.oauth = { state, nonce, verifier };
res.redirect(AUTH_URL + "?" + new URLSearchParams({
response_type: "code", client_id: CLIENT_ID, redirect_uri: REDIRECT_URI,
scope: "openid", state, nonce,
code_challenge: challenge, code_challenge_method: "S256",
}));
});
On the callback, compare state with the stored value, send the stored verifier in the token request, check nonce inside the ID token, then delete all three so they cannot be reused.
How to test it
Start a login in one browser and replay the callback URL in another. Expect a rejection. Replay the same callback twice. Expect the second to fail. Change state by one character and expect failure.
Common mistakes
- A constant or timestamp based
state. - Storing the verifier in a cookie the attacker can set.
- Never deleting the values after use.
Quick check
Why store the values server side?
So the callback can be matched to the exact browser session and transaction that began the flow.