Cross-Site Request Forgery
CSRF exploits the fact that browsers attach cookies to outbound requests automatically. A malicious site can trigger a state-changing request to your application using the victim's authenticated session.
The attack
The victim is logged into bank.example. They visit a malicious page that contains:
<form action="https://bank.example/transfer" method="POST">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="1000">
</form>
<script>document.forms[0].submit()</script>
The browser submits the form. The bank's session cookie comes along. The transfer happens.
The modern defaults
1. SameSite cookies
The first line of defense. Cookies are attached to cross-site requests only when their SameSite attribute permits it.
| Value | Behavior |
|---|---|
Strict | Never sent on cross-site requests. Strongest, but breaks legitimate cross-site navigation. |
Lax (default in modern browsers) | Sent only on top-level navigations (clicking a link). Blocks form POSTs from other sites. |
None | Always sent. Requires Secure; used when cross-site embedding is needed. |
Modern browsers default to Lax when no attribute is set, which alone defeats the example above.
2. Anti-CSRF tokens
Embed a server-issued, per-session secret in every state-changing form. The browser cannot read it from a third-party context.
- Synchronizer token. Server stores the token alongside the session and compares on submit.
- Double-submit cookie. Token is sent in both a cookie and a request header/body; server compares them.
- Encrypted token. Token is a signed value tying the request to the session — stateless.
3. Custom request headers
Browsers won't let cross-origin requests send custom headers without a CORS preflight. Requiring a header like X-Requested-With: XMLHttpRequest on state-changing endpoints turns CSRF into a CORS problem the browser already protects against.
4. Origin / Referer validation
For sensitive endpoints, check the Origin or Referer header matches your domain. Belt-and-braces, especially behind reverse proxies.
GET requests should not change state. If they did, attackers could trigger them with <img> tags, bypassing many CSRF defenses. This is a long-standing HTTP convention — and a hard rule for safe API design.