1

Concept

Synchronizer Tokens

The synchronizer token pattern is one of the foundational defenses against CSRF.

The basic idea is simple: the application generates a random, unpredictable value and gives it to the legitimate page, typically by embedding it in a hidden form field. The server maintains enough state or otherwise uses a validation mechanism to verify that the submitted token is the one associated with the user's session or request.

Every protected state-changing request must include that token.

User requests legitimate page
        ↓
Server generates or retrieves a CSRF token associated with
that authenticated context
        ↓
Token embedded in the page
        ↓
User submits form
        ↓
Token returned with request
        ↓
Server validates token
        β”œβ”€β”€ Valid β†’ Process request
        └── Missing / Invalid β†’ Reject request

For example:

<form action="/api/account/password" method="POST">
  <input type="hidden"
         name="csrf_token"
         value="a9f3e7c1b2d84f...">

  <input type="password"
         name="newPassword">
</form>

Why the Attacker Cannot Simply Copy the Token:

This is the part that connects directly back to Same-Origin Policy and ambient authority from section what is csrf

An attacker-controlled page may be able to cause the victim's browser to send a request to the target application, andβ€”if the applicable cookie rules permit itβ€”the browser may automatically attach the victim's session cookie.

But the attacker still needs the matching CSRF token.

Attacker controls:

evil.example.com
        ↓
Can construct:POST /api/account/password
newPassword=attacker-choice
        ↓
Browser may provide:session=victim-session
        ↓
But attacker also needs: csrf_token=???

The attacker cannot simply fetch the legitimate form and read its token:

fetch("https://app.example.com/account/password")

Even if the browser sends a request in some circumstances, JavaScript running on evil.example.com cannot normally read the response body from app.example.com unless the target explicitly permits that cross-origin access.

The attacker therefore cannot extract:

<input type="hidden"
       name="csrf_token"
       value="a9f3e7c1b2d84f...">

from the legitimate page.

This creates the critical asymmetry behind the defense:

Attacker can control:

βœ“ Destination
βœ“ HTTP method
βœ“ Request parameters
βœ“ Request timing

Browser may provide:

βœ“ Victim's session cookie when applicable

Attacker cannot obtain: Valid CSRF token

The forged request therefore becomes:

Authenticated?
Possibly yes.
        ↓
CSRF token valid? No.
        ↓
  Request rejected

CSRF exists because authentication cookies can act as ambient authority: the browser may automatically provide the credential when a request is sent to the appropriate destination.

The synchronizer token adds something that is not ambient.

Session cookie

Automatically supplied by browser when applicable
        +
CSRF token

Must be explicitly included and validated
        ↓
Authenticated request with proof tied to the legitimate
application context

The central principle is:

A cross-site attacker may be able to make the victim's browser send an authenticated request, but they cannot complete that request successfully if the server requires an additional unpredictable value that the attacker cannot read, predict, or otherwise obtain.

2

Concept

Per-Session Tokens

The simplest synchronizer-token implementation generates one random token when the session begins and reuses that same value for the lifetime of the session.

Every form rendered during that session receives the same token:

Session begins
        ↓
Server generates: csrf_token = a9f3e7c1b2d84f...
        ↓
User visits:
/profile
/account/password
/account/email
/settings
        ↓
Every protected form contains:
csrf_token = a9f3e7c1b2d84f...

This approach is lightweight.

The application only needs to generate, associate, and validate one token for the session:

One session
        ↓
One CSRF token
        ↓
Same token reused for every protected request

For many applications, this is a perfectly reasonable design. The token is still unpredictable, and a cross-origin attacker still cannot normally obtain it simply by knowing the victim has an active session.

The trade-off is lifetime.

Because the same token remains valid for the entire session, exposure of that value can remain useful for as long as both the token and its associated session remain valid.

Session begins
        ↓
CSRF token issued
        ↓
Token accidentally exposed
        ↓
Same token remains valid
        ↓
Potentially usable until:

Session expires
        OR
Session is invalidated
        OR
Token is rotated

Potential exposure paths can include implementation mistakes such as:

  • Logging request parameters containing the token without redaction.

  • Including the token in a URL, allowing it to propagate through browser history, logs, or referrer handling.

  • Exposing page contents to an untrusted third party through an unsafe integration.

  • Accidentally revealing the token through debugging or error output.

Per-session token leaks once
        ↓
Attacker obtains: csrf_token = valid-value
        ↓
Same token reused by every protected request
        ↓
Attacker may continue using it
        ↓
Until the session or token is invalidated or rotated

This is different from a design where a token is valid only for a narrower scope, such as a single request or a short-lived action.

Reuse and Compression-Oracles:

A more subtle concern arises when the same secret value is repeatedly reflected into compressed responses alongside attacker-controlled input.

This is the general class of issue exploited by compression-oracle attacks such as BREACH and, in earlier transport-layer contexts, CRIME.

The basic idea is:

A response contains:

Secret value
        +
Attacker-controlled text
        ↓
The response is compressed
        ↓
The attacker changes their input and observes the compressed result
        ↓
Compression behavior reveals clues about whether the attacker's input
matches part of the secret
        ↓
After many carefully chosen requests, the secret may be recovered

The important point is that the attacker does not need to read the response body directly.

Instead, the attacker observes a side channel: usually the size of the compressed response, or some related measurement.

Why Per-Session Tokens Provide a Stable Target:

A per-session token is reused across many responses:

Page 1:
csrf_token=a9f3e7c1

Page 2:
csrf_token=a9f3e7c1

Page 3:
csrf_token=a9f3e7c1

That consistency is convenient for the application, but it also gives an attacker a stable secret to target.

Same token appears repeatedly
        ↓
Attacker can perform many measurements
        ↓
Each response provides another opportunity to test a candidate prefix
        ↓
The target remains unchanged while the attacker improves their guesses

If the token changed after every request, the attacker would have a much harder time combining measurements:

Request 1:
Token = a9f3e7c1

Request 2:
Token = 4b82d0fa

Request 3:
Token = 91c6aa20

A measurement that reveals information about one token would not necessarily help recover the next token.

3

Concept

Per-Request Tokens

A per-request token uses a narrower validity window than a per-session token.

Instead of generating one token and reusing it throughout the entire session, the application generates fresh tokens for individual forms, requests, or actions.

User opens Form A
        ↓
Server issues: csrf_token = token-A

User opens Form B
        ↓
Server issues: csrf_token = token-B


User submits Form A
        ↓
Server validates: token-A

This reduces the value of token reuse.

A token exposed while interacting with one form does not automatically become a universal credential for every state-changing request in the session.

The exact scope depends on the implementation. A token may be:

  • Generated for each form render.

  • Bound to a particular action or endpoint.

  • Valid only for a short period.

  • Valid for one successful use only.

  • Part of a small set of simultaneously valid tokens.

These designs are related, but they are not identical.

If the token changes frequently, an attacker cannot necessarily collect many measurements against the same stable value.

Request 1:
csrf_token = a9f3...

Request 2:
csrf_token = 4b82...

Request 3:
csrf_token = 91c6...

A measurement against one token does not automatically help recover the next.

As with the discussion of compression oracles in the previous section, however, frequent rotation is not by itself a universal defense. The full exploitability still depends on the application's response structure, compression behavior, attacker-controlled input, and the availability of a measurable side channel.

The Usability Trade-Off:

The main disadvantage of strict single-use tokens is that browsers and users do not always interact with applications in a simple, linear sequence.

Consider the browser's Back button:

User opens form
        ↓
Server issues: token-A
        ↓
User submits form
        ↓
token-A consumed
        ↓
User clicks Back
        ↓
Browser displays cached form
        ↓
Form still contains: token-A
        ↓
User submits again
        ↓
  Token already invalid

From the user's perspective, nothing obviously suspicious happened. They simply returned to a page they had already visited.

Multiple tabs can create a similar problem.

Tab 1: Form with token-A

Tab 2: Same form with token-B
        ↓
User submits Tab 1
        ↓
Depending on the implementation, token A or potentially a broader
set of related token state is consumed or replaced
        ↓
User submits Tab 2
        ↓
Possible result: Invalid CSRF token

The exact behavior depends on how narrowly the application scopes each token.

A poorly designed implementation might invalidate every outstanding token whenever one form is submitted, making ordinary multi-tab use unnecessarily frustrating.

A more practical design can allow multiple independently valid tokens to coexist:

Open Form A β†’ token-A

Open Form B β†’ token-B

Open Form C β†’ token-C
        ↓
All remain valid temporarily
        ↓
Each token:

βœ“ Has limited lifetime
βœ“ May be scoped to an action
βœ“ Can expire independently
βœ“ Can optionally become invalid
  after successful use

This avoids forcing the application to choose between strong token rotation and ordinary browser behavior.

The Operational Cost:

Per-request or short-lived tokens also require more state and more careful validation logic than a single session-wide value.

A Practical Middle Ground:

Many applications use a middle ground rather than choosing between:

One token for the entire session

and:

One token
        ↓
Exactly one use
        ↓
Immediately destroyed

A practical design may use tokens that are:

βœ“ Random and unpredictable

βœ“ Valid for a limited period

βœ“ Refreshed periodically

βœ“ Bound to the user's authenticated context

βœ“ Optionally scoped to sensitive actions

βœ“ Able to coexist with a limited number
  of other valid tokens

The appropriate design depends on the application's sensitivity and architecture.

A low-risk application may reasonably use a single per-session synchronizer token. A high-sensitivity action such as changing a password, changing an account recovery email, or authorizing a financial transaction may justify a shorter-lived or action-specific token, often alongside fresh authentication.

4

Concept

Token Validation Weaknesses

A CSRF token appearing in the HTML provides no protection by itself.

The actual security boundary is the server-side validation logic.

Page contains:

csrf_token=a9f3e7c1...
        ↓
Request submitted
        ↓
Server must verify:
Is this the correct token for this request context?
        ↓
 Yes β†’ Process request
 No  β†’ Reject request

If that validation step contains a gap, the token may be nothing more than decorative markup.

Several implementation mistakes appear repeatedly.

Checking presence, Not value:

The most basic failure is checking only whether a token exists.

For example:

Request contains csrf_token?
        ↓
Yes
        ↓
βœ“ Accept request

This does not provide CSRF protection.

An attacker can submit any arbitrary value:

csrf_token=hello

or:

csrf_token=attacker-invented-value

and still satisfy the check.

A vulnerable implementation might conceptually behave like:

if (!empty($_POST['csrf_token'])) {
    processRequest();
}
Submitted token: attacker-value
        ↓
Expected token: a9f3e7c1...
        ↓
Compare
        ↓
  No match
        ↓
Reject request

A CSRF token only works if its value is actually validated.

Tokens Not Bound to the Session:

A more subtle failure occurs when the application validates that a submitted value is a valid token, but not that it belongs to the user or session making the request.

Imagine the application maintains a global collection of valid tokens:

Valid tokens:

token-A
token-B
token-C
token-D

An attacker can legitimately obtain one of those tokens from their own session:

Attacker logs into:

Their own account
        ↓
Receives: token-A

They then attempt to use that token in a forged request against a victim.

If the server only asks:

Is token-A valid?
        ↓
βœ“ Yes

the request may succeed even though the token was never issued to the victim.

A token obtained legitimately by one user should not automatically become valid authorization for every other user's session.

Token Stored Only in a Cookie:

Another common mistake is placing the CSRF token in a cookie and then treating the browser's automatic delivery of that cookie as proof that the request is legitimate.

For example:

Browser stores:

Cookie:
csrf_token=a9f3e7c1...

Then the application simply checks:

Did the request include a csrf_token cookie?
        ↓
βœ“ Yes
        ↓
Accept request

This recreates the exact ambient-authority problem that CSRF defenses are supposed to solve.

Victim visits: evil.example.com
        ↓
Attacker triggers request to: app.example.com
        ↓
Browser automatically attaches:
session=victim-session
csrf_token=a9f3e7c1...
        ↓
Server sees both cookies
        ↓
  Forgery may succeed

The attacker never needed to know either value.

Double-Submit Cookies:

This is the problem the double-submit cookie pattern is designed to address.

The server gives the browser a CSRF token in a cookie:

Cookie:
csrf_token=a9f3e7c1...

But the application also requires the client to explicitly send the same value somewhere else, such as a request body or custom header:

Cookie:
csrf_token=a9f3e7c1...
        +
Request body:
csrf_token=a9f3e7c1...

The server checks that the two values match:

Cookie token
        ↓
a9f3e7c1...
        compared with
Submitted token
        ↓
a9f3e7c1...
        ↓
  Match β†’ Continue
  Mismatch β†’ Reject

To succeed this attacker must not be able to set or control the CSRF cookie for the target application.

For example, overly broad cookie scoping or an attacker-controlled subdomain can undermine a naive implementation if the attacker can cause a chosen cookie value to be accepted by the target.

Attacker controls:

evil.example.com
        ↓
Able to influence a cookie within an overly broad domain scope
        ↓
Attacker chooses: csrf_token=attacker-value
        ↓
Forged request includes:
Cookie:
csrf_token=attacker-value

Body:
csrf_token=attacker-value
        ↓
Values match
        ↓
⚠ Potential bypass

For this reason, robust double-submit designs should ensure that the attacker cannot inject a cookie of their choosing into the validation scope. Cookie scoping and domain control therefore matter just as much as the comparison itself.

Inconsistent Enforcement Across Endpoints:

CSRF protection is also frequently implemented correctly in one part of an application and missing entirely somewhere else.

For example:

Web application

POST /account/email
        ↓
βœ“ CSRF token required

Later, a separate API endpoint is added:

POST /api/account/email
        ↓
❌ No CSRF validation

This is especially common when an application has multiple interfaces:

  • Traditional server-rendered forms.

  • JSON APIs.

  • Mobile-specific endpoints.

  • Legacy endpoints.

  • GraphQL mutations.

  • Newly added microservices or backend routes.

You've completed Cross Site Request Forgery

Great work β€” explore other topics to keep learning.