csrf-tokens

How Servers Handle CSRF Tokens: Generation, Validation, and Best Practices

Welcome to Part 2 of the CSRF series!
While spotting CSRF vulnerabilities during testing or bug bounties is often straightforward, have you ever paused to think about what really happens behind the scenes when implementing mitigations?

In Part 1, we explored the fundamentals of Cross-Site Request Forgery (CSRF), why it’s dangerous, and how browsers now defend against it using mechanisms like SameSite cookies and CORS. But browser-level protections are not always enough. To fully secure your application, especially for POST-based form submissions and API endpoints — you need to implement CSRF tokens.

In this part, we’ll dive deep into:

  • What CSRF tokens are
  • How they’re generated and validated
  • Why they must be generated server-side
  • Where to store them safely
  • How to eliminate the need for database lookups using HMAC or encrypted token techniques

What Are CSRF Tokens?

A CSRF token is a unique, unpredictable, and secure value generated by the server and sent to the client. When the client submits a request (especially modifying ones like POST), it must include that token. The server then verifies the token before executing the request.

This mechanism ensures that the request originated from a legitimate user interaction on the actual website — not from an attacker-controlled third-party page.

Why tokens are generated Server-Side?

The server — not the client — must generate the CSRF token. This is because:

  • The server is the trusted source in the communication.
  • The token must be bound to the user’s session, ensuring that it’s unique per user.
  • Only the server can validate that the incoming token is authentic.

Step-by-Step CSRF Token Flow

Let’s walk through a typical flow of how CSRF tokens work in a secure web application.

1. Token Generation

When a user visits a page with a form or API endpoint:

  • The server generates a random CSRF token — often a UUID or cryptographically secure string.
  • This token is stored server-side

2. Token Distribution

The CSRF token is sent to the client in one of two safe ways:

  • Embedded in an HTML form as a hidden field:
<input type="hidden" name="csrf_token" value="abc123xyz">
  • Exposed via JavaScript for AJAX requests, usually through a templating engine or meta tag:
<meta name="csrf-token" content="abc123xyz">

3. Client Submits Request

When the user submits the form or makes an API call:

  • The CSRF token is included in the request body (for forms) or in a custom HTTP header (for AJAX):
POST /update-profile HTTP/1.1
X-CSRF-Token: abc123xyz

4. Server Validates the Token

The server compares the submitted token with the one it issued to the session:

  • If it matches → The request is considered valid.
  • If it doesn’t match or is missing → The request is blocked with a 403 Forbidden response.

Where Should You Store the Token?

To be effective and safe from tampering, CSRF tokens must not be stored in insecure or easily accessible places like:

  • localStorage (accessible via JavaScript and vulnerable to XSS)
  • Query strings (visible in URLs and logs)

Instead, use:

  • Hidden form fields (safe from cross-site reads)
  • Secure cookies (preferably HttpOnly and SameSite)
  • Custom HTTP headers for API requests (e.g., X-CSRF-Token)

How to Avoid Database Lookups: Stateless CSRF Tokens

A common challenge with CSRF tokens is storing them per session, especially in high-scale or stateless environments where storing session data in a database can be expensive or undesirable.

To solve this, you can use stateless, self-validating tokens generated with HMAC (Hash-based Message Authentication Code) or encryption.

Stateless CSRF Tokens with HMAC

These tokens include all the information needed for validation, so no server-side lookup is required. This can be attributed to how a jwt works.

Structure of the Token:

csrf_token = base64(user_id + timestamp + HMAC(user_id + timestamp, secret_key))
  • user_id: Tied to the session or authentication state
  • timestamp: Used for token expiration
  • HMAC(...): A secure signature to ensure the token hasn’t been tampered with
  • secret_key: Stored securely on the server

Example:

Let’s say:

  • user_id = 789
  • timestamp = 1715000000
  • secret_key = myapp_secret

The HMAC would be:

HMAC("7891715000000", "myapp_secret")

Then, concatenate and base64-encode the whole token.


Stateless Token Validation Flow

When the server receives a request with a CSRF token, It does the following.

  1. Decode the token
  2. Extract the user_id and timestamp
  3. Recompute the HMAC using the same algorithm and secret key
  4. Compare the computed HMAC with the one provided in the token
  5. Check the timestamp to ensure the token is within an acceptable time window (e.g., 30 minutes)

If all checks pass, the token is valid.

Advantages of Stateless Tokens

  • No need for session storage or database lookups
  • Highly scalable for distributed systems or APIs
  • Secure, provided the secret key remains confidential

CSRF Token Expiration

To add another layer of protection, always include a timestamp in the token and reject tokens older than a certain threshold.

This ensures:

  • Stolen tokens are only valid for a limited time
  • Replay attacks are limited in scope

Summary: Best Practices for CSRF Tokens

Best PracticeWhy It Matters
Generate tokens server-sideEnsures trust and integrity
Use strong randomness or HMACPrevents guessing and forgery
Tie token to session/userPrevents token reuse across accounts
Embed in forms or send via headersAllows proper validation
Use stateless HMAC tokens when scalingAvoids performance hits from DB lookups
Enforce token expirationLimits risk of token theft
Do not store tokens in localStoragePrevents XSS-based leakage

By combining browser-level defenses (discussed in Part 1) with robust token-based validation, your application can be effectively protected from CSRF attacks. Whether you’re building a monolith or a stateless microservice architecture, implementing CSRF mitigation is a non-negotiable step in web application security.

I hope you enjoyed reading this blog. See you in the next one. Until then, Happy defending!

shreyapohekar

I’m Shreya Pohekar, a Senior Product Security Analyst at HackerOne. I enjoy sharing my thoughts and insights through blogging, turning complex security topics into engaging and accessible content for my readers.

Leave a Reply