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
andSameSite
) - 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 statetimestamp
: Used for token expirationHMAC(...)
: A secure signature to ensure the token hasn’t been tampered withsecret_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.
- Decode the token
- Extract the user_id and timestamp
- Recompute the HMAC using the same algorithm and secret key
- Compare the computed HMAC with the one provided in the token
- 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 Practice | Why It Matters |
---|---|
Generate tokens server-side | Ensures trust and integrity |
Use strong randomness or HMAC | Prevents guessing and forgery |
Tie token to session/user | Prevents token reuse across accounts |
Embed in forms or send via headers | Allows proper validation |
Use stateless HMAC tokens when scaling | Avoids performance hits from DB lookups |
Enforce token expiration | Limits risk of token theft |
Do not store tokens in localStorage | Prevents 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!