skip to content

A booking form posts a hidden field holding an unpredictable value the server stored against the session - what does checking it prove?

level: juniorimportance: must knowfreq 74%

answer

  1. provenance, not identity
  2. unpredictable and stored server-side
  3. another site cannot read your page
  4. hidden means unrendered, not secret
  5. compare the two in constant time

basics

~20 s

A matching value proves the request was composed by a page this site served: the value is unpredictable and its authoritative copy lives in server-side session state, so a document from elsewhere can neither read nor guess it.

solid answer

~40 s

The server generates an unpredictable value for the session, keeps the authoritative copy in server-side session state, and writes it into every page that renders a form for an unsafe method. When the form comes back, the server compares the submitted value against the stored one and refuses the request when they differ. A match proves **provenance**: the request was composed by a document this site served, because only such a document could read the value out of the page. It proves nothing about identity - the session cookie is what names the user, and the browser attaches that cookie to a forged request just as willingly. The two checks answer different questions: `which account is this?` and `which document composed this request?`

code

html · 7 lines
html
<form method="post" action="/bookings/confirm">
  <input type="hidden" name="csrf_token"
         value="8f4a1c3e9b2d7f60a5c81e4b93d2f7a1">
  <input type="hidden" name="slotId"
         value="hall-a-2026-05-04-1900">
  <button type="submit">Confirm booking</button>
</form>

go deeper

for a junior

Recall the shape: an unpredictable value the server made, kept against the session, written into the form, and compared when the form comes back. Say out loud that it proves where the request came from, not who sent it.

for a middle

Explain why a document on another host cannot obtain the value: it can emit any body it likes, but the browser will not let it read your page. Be able to separate the cookie's job from the token's job in one sentence each.

for a senior

Show the operational edges: the value must never travel in a URL, the comparison must be constant time, and the scheme assumes no injected script is already running on your origin. State the assumption rather than letting it pass silently.

for a principal

Frame it as buying provenance at the cost of server-side state per session, and be explicit that the guarantee ends where your own origin's integrity ends. Anything that can execute in your pages holds the value too.

## What the value actually is A **synchronizer token** is a value with three properties, and every one of them is load-bearing: - it is **unpredictable** - drawn from a cryptographically strong random source and long enough that guessing is hopeless, with 128 bits of randomness the usual floor; - it is **bound to the session, not to the form** - the authoritative copy lives in server-side state keyed by the session, which is why every page rendered for that session can carry the same value and why possession of it means something; - it is **written into the page by the server** - into a hidden input in each form that performs an unsafe request. "Unsafe" is the specification's own split rather than a house convention: `GET`, `HEAD`, `OPTIONS` and `TRACE` are the safe methods, and a request using anything else may change state. A hall-booking system therefore checks the value on the confirm step, on a cancellation and on an edit, not on the page that merely lists which rooms are free. ## Why a document from another site cannot supply it The thing being defended against is a document your site never served, hosted somewhere you do not control, that causes a request to be sent to your site. The browser attaches the session cookie to that request, because attaching a stored cookie to a request bound for its host is exactly what a browser does. The credential is attached by the **user agent**, not by the page's own code, and that ambient attachment is the entire problem: the request is genuine, the session is genuine, only the intent is forged. The hostile document can put whatever fields it likes into the body it emits, including a field with the right name. What it cannot do is **read your page**. A browser will not hand the content of a document from one origin to a document from another, so the attacker can neither lift the value out of the hidden input nor read it out of a response your server sent. Guessing is the only avenue left, and an unpredictable value of adequate length closes it. Note what "hidden" means here and what it does not. A hidden input is *not rendered*; it is not *not present*. The value sits in the page source where the user can read it, and that is fine, because the user is not the adversary. The adversary is a different document, and it is the cross-origin read restriction - not the word "hidden" - that keeps the value away from them. ## What a match proves, and what it does not | the session cookie | the synchronizer token | |---|---| | attached by the browser to every request bound for the host | supplied only by a document that could read the page | | answers "which account is this?" | answers "which document composed this request?" | | perfectly genuine on a forged request | absent or wrong on a forged request | So the check is a **provenance check**, not an authentication check, and conflating the two is the most common misreading. A request carrying a valid token but no session is still unauthenticated and must be refused as such. A request carrying a valid session but no token is authenticated and unattributable, which is precisely the shape of a forgery. ## Comparing the submitted and the stored value The server side is three steps: 1. **Look up the stored value** for the session. If there is none, there is nothing to compare against, and the request is refused. 2. **Read the submitted value** from the request. 3. **Compare the two in constant time** - a comparison whose duration does not depend on how many leading bytes matched. An ordinary comparison that returns at the first differing byte leaks that count, and someone able to submit many attempts and measure the response time can walk a secret out a byte at a time. Over a network the signal is weak, but a constant-time comparison costs nothing, so there is no reason to leave the channel open. ## Where the scheme still fails - **Script running on your own origin.** Anything executing inside your page can read the value as easily as your own code can. The token assumes your pages are not already executing someone else's script; it is not a defence against that. - **A value that escapes the page.** Put it in a URL and it lands in access logs, in browsing history, and in whatever a user pastes into a chat window. The body or a header field is where it belongs. - **A value shared across sessions.** A constant baked into the deployment is readable by anyone who can open a single page, attacker included. Binding to the session is what makes possession evidence of anything.

  • The page already requires a login cookie. Why is a second value needed at all?
    Because the browser attaches the cookie to any request bound for your host, whoever composed it. The cookie establishes which account the request runs as; it cannot establish which document built the request. The stored value is the only part of the request that a document your site did not serve is unable to supply.
  • Why must the submitted and stored values be compared in constant time?
    An ordinary comparison stops at the first differing byte, so its duration reveals how many leading bytes were right. Someone able to submit repeated attempts and time the responses can recover the value byte by byte. A constant-time comparison always inspects the full length, so the duration carries no information about the content.
  • Does the value need to differ per user, or can one value serve the whole deployment?
    It must be per session. A deployment-wide constant is readable by anyone who can open one page of the site, including an attacker with their own account, so possessing it proves nothing. Binding the value to the session is what makes "this request carried the right value" evidence about where the request came from.

It is the numbered ticket the hall's own desk prints when you check a coat in. Anyone can walk up to the counter, but only someone the desk actually served can quote the number - and the number says nothing about who they are.

saying these in an interview costs you the question

  • Says the hidden value proves who the user is
  • Thinks the field being hidden is what protects the value
  • Believes a predictable value such as a counter or timestamp is adequate
  • Claims the token still protects a page that already runs injected script
  • Says a single deployment-wide value is fine if it is long enough
  • Reads a missing value as a login failure rather than a provenance failure