โ† Back to blog

Understanding CSRF: Why Cookies Aren't Enough

ยท3 min read
authenticationcsrfCORSWeb DevelopmentSoftware Engineering

The Problem

Imagine you're logged into bank.com. While browsing the web, you visit evil.com which contains this innocent-looking link:

Win a Free iPhone! ๐ŸŽ

When you click, your browser sends the request to bank.com with your authentication cookies automatically attached. The bank sees a legitimate session and processes the transfer. You've been attacked without knowing it.

This is Cross-Site Request Forgery (CSRF).


How It Works: The Cookie Problem

The vulnerability exists because browsers automatically include cookies with every request to a domain, regardless of where the request originated.

You visit evil.com

โ†“

evil.com triggers: POST https://bank.com/transfer

โ†“

Browser automatically sends: Cookie: sessionId=abc123

โ†“

bank.com sees valid session โ†’ Processes request โŒ

The server can't tell the difference between:

Both include the same authentication cookies.


The Solution: CSRF Tokens

The defense adds a second layer: a CSRF token that evil.com cannot obtain.

How It Works

  1. Server generates a unique, random token for each user session

  2. Token is stored server-side (tied to the user's session)

  3. Token is sent to the client (in HTML or sessionStorage)

  4. Client includes token in every sensitive request

  5. Server validates: "Does the token in the request match the user's session token?"

Example Flow

Legitimate request:

// bank.com loads โ†’ stores token in sessionStorage 
sessionStorage.setItem('csrf_token', 'abc123xyz');

// User submits form 
fetch('/transfer', { 
    method: 'POST', 
    headers: { 
        'X-CSRF-Token': sessionStorage.getItem('csrf_token') // "abc123xyz"
    }, 
    body: JSON.stringify({to: 'friend', amount: 100}) 
});

// Server validates: 
// - Cookie: sessionId=user_1 โ†’ Session has token: "abc123xyz" 
// - Header: X-CSRF-Token: "abc123xyz" 
// - Match! โœ… Process request

Attack attempt:

// evil.com tries to forge request 
fetch('https://bank.com/transfer', { 
    method: 'POST', 
    body: JSON.stringify({to: 'attacker', amount: 1000}) 
});

// Browser sends cookies automatically 
// But NO csrf_token header (evil.com doesn't know the token)

// Server validates: 
// - Cookie present โœ… 
// - CSRF token? โŒ Missing or wrong 
// - REJECT! (403 Forbidden)

Why attackers can't steal the token:

The token is protected by the Same-Origin Policy:

  • evil.com can't read bank.com's sessionStorage (browser isolates storage per domain)

  • evil.com can't fetch bank.com pages and read the response (CORS blocks it)

  • Token is unpredictable (randomly generated, unique per session)

Without the token, the forged request fails.


Diagram: CSRF Attack vs. Defense


CSRF vs. CORS: What's the Difference?

Common confusion: "Don't CORS protections prevent CSRF?"

No. They protect different things:

CSRFCORS
Prevents unwanted actionsControls reading responses
Attacker triggers request to harm youAttacker tries to read your data
Defense: CSRF tokensDefense: CORS headers

Key Insight

CORS doesn't prevent CSRF because:

  • CORS blocks reading cross-origin responses

  • CSRF attacks don't need to read the response

  • The damage is done when the request executes (transfer money, delete account, etc.)

Even with CORS errors in the console, the CSRF attack succeeds:

Browser console on evil.com: โŒ CORS error: Response blocked

Network tab: โœ… POST /transfer - 200 OK

Result: Money transferred, but evil.com can't read the response


Conclusion

CSRF exploits the browser's automatic cookie behavior. The defense is simple but powerful:

Cookies alone = Authentication only ("Who is making the request?")

Cookies + CSRF Token = Authentication + Intent verification ("Who is making the request AND did they really mean to?")

Modern best practices:

  1. Use CSRF tokens for all state-changing requests (POST, PUT, DELETE)

  2. Set SameSite=Lax on cookies (modern browsers default to this)

  3. Store tokens in sessionStorage for SPAs (cleared when tab closes)

  4. Never rely on cookies alone for sensitive actions

Remember: If a request can change data, protect it with a CSRF token.

โ† Back to all posts