Cross-Site Request Forgery (CSRF) Attacks

Medium•

CSRF attacks exploit the trust a website has in a user's browser. Understanding prevention mechanisms is essential for secure web applications.

Quick Decision Guide

Quick Protection Guide:

Express.js: Use csurf middleware: app.use(csurf()) then add req.csrfToken() to forms.

SameSite Cookies: Set SameSite=Strict on session cookies - prevents cross-site cookie sending: cookie: { sameSite: 'strict', secure: true, httpOnly: true }

CSRF Token Pattern: 1. Generate token on server: const token = generateToken() 2. Store in session: req.session.csrfToken = token 3. Include in form: <input type="hidden" name="_csrf" value="${token}"> 4. Validate on POST: if (req.body._csrf !== req.session.csrfToken) return 403

Protect: All state-changing requests (POST, PUT, DELETE). GET requests are generally safe but validate if they change state.

Common Mistake: Only protecting some endpoints - protect ALL endpoints that modify data.

Understanding CSRF Attack Process

Overview of CSRF Attacks

CSRF (Cross-Site Request Forgery) is an attack that forces authenticated users to execute unwanted actions on a web application. The attack exploits the browser's automatic cookie-sending behavior.

How CSRF Works

Figure 1: CSRF Attack Flow

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”         ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”         ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│   User      │         │  Attacker    │         │   Bank.com  │
│  Browser    │         │   Website    │         │   Server    │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”˜         ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜         ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
       │                       │                        │
       │ 1. Login              │                        │
       ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€>                        │
       │                       │                        │
       │ <─────────────────────── Session Cookie        │
       │                       │                        │
       │ 2. Visit              │                        │
       ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€>                        │
       │                       │                        │
       │ 3. Malicious Form     │                        │
       │ <───────────────────────                        │
       │                       │                        │
       │ 4. Auto-submit        │                        │
       │    (with cookies)      │                        │
       ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€>
       │                       │                        │
       │                       │                        │ 5. Process
       │                       │                        │    Request
       │                       │                        │
       │ <─────────────────────────────────────────────────
       │                       │                        │
       │ 6. Money Transferred  │                        │
       │                       │                        │

Attack Steps Explained

Step 1: User Authentication

•User logs into trusted site (e.g., bank.com)
•Server sets session cookie in browser
•Cookie stored with domain: bank.com

Step 2: User Visits Malicious Site

•User navigates to attacker.com (via email, link, etc.)
•Browser still has bank.com session cookie

Step 3: Malicious Site Triggers Request

•Attacker's page contains form/script targeting bank.com
•Form auto-submits or executes via JavaScript

Step 4: Browser Sends Request

•Browser automatically includes bank.com cookies
•Request appears legitimate (comes from authenticated user)
•Server processes request without additional verification

Step 5: Unwanted Action Executed

•Money transfer, password change, or other state change
•User unaware of the attack

CSRF Prevention Methods

1. CSRF Tokens

How it works:

•Server generates unique token per session
•Token included in forms/requests
•Server validates token on each request
•Attacker can't guess token (not accessible via JavaScript)

Figure 2: CSRF Token Protection Flow

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”         ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”         ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│   User      │         │  Attacker    │         │   Bank.com  │
│  Browser    │         │   Website    │         │   Server    │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”˜         ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜         ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
       │                       │                        │
       │ 1. Request Form        │                        │
       ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€>
       │                       │                        │
       │ <─────────────────────────────────────────────────
       │    Form + CSRF Token  │                        │
       │    (abc123xyz)        │                        │
       │                       │                        │
       │ 2. Submit Form        │                        │
       │    (with token)       │                        │
       ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€>
       │                       │                        │
       │                       │ 3. Try to submit      │
       │                       │    (no token)          │
       │                       ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€>
       │                       │                        │
       │                       │ <───────────────────────
       │                       │    403 Forbidden       │
       │                       │                        │
       │ <─────────────────────────────────────────────────
       │    200 Success        │                        │
       │                       │                        │

Implementation:

// Generate token
const token = generateRandomToken();
session.csrfToken = token;

// Include in form (template example)
<form>
  <input type="hidden" name="_csrf" value="[TOKEN_VALUE]" />
</form>

// Validate on server
if (req.body._csrf !== session.csrfToken) {
  return res.status(403).json({ error: 'Invalid CSRF token' });
}

2. SameSite Cookie Attribute

How it works:

•Cookies marked with SameSite=Strict or SameSite=Lax
•Browser prevents sending cookies in cross-site requests
•Modern browsers support this

Figure 3: SameSite Cookie Protection

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”         ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”         ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│   User      │         │  Attacker    │         │   Bank.com  │
│  Browser    │         │   Website    │         │   Server    │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”˜         ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜         ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
       │                       │                        │
       │ 1. Login              │                        │
       ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€>                        │
       │                       │                        │
       │ <─────────────────────── Cookie                │
       │    (SameSite=Strict)  │                        │
       │                       │                        │
       │ 2. Visit              │                        │
       ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€>                        │
       │                       │                        │
       │ 3. Try CSRF Request   │                        │
       │                       ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€>
       │                       │    (No Cookie Sent)    │
       │                       │                        │
       │                       │ <───────────────────────
       │                       │    401 Unauthorized    │
       │                       │                        │

Implementation:

// Set cookie with SameSite
res.cookie('session', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict'
});

3. Double Submit Cookie

How it works:

•Cookie value matches form token
•Attacker can't read cookie (httpOnly)
•Server compares cookie and form token

Implementation:

// Set cookie with random value
const token = generateRandomToken();
res.cookie('csrf-token', token, { httpOnly: false });

// Include same value in form
<form>
  <input type="hidden" name="_csrf" value="[TOKEN_VALUE]" />
</form>

// Validate: cookie must match form value
if (req.cookies['csrf-token'] !== req.body._csrf) {
  return res.status(403).json({ error: 'Invalid CSRF token' });
}

CSRF Prevention Methods Comparison

MethodSecurity LevelComplexityBrowser SupportUse Case
CSRF Tokens⭐⭐⭐⭐⭐ HighestMediumAll browsersMost reliable, works everywhere
SameSite Cookies⭐⭐⭐⭐ HighLowModern browsersEasy to implement, good default
Double Submit Cookie⭐⭐⭐ MediumLowAll browsersSimple apps, less secure

Best Practices for Implementation

Start with SameSite Cookies

Initial Setup:

•Set SameSite=Strict on all session cookies
•Use secure flag in production (HTTPS only)
•Use httpOnly flag to prevent JavaScript access

Example:

app.use(session({
  cookie: {
    sameSite: 'strict',
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true
  }
}));

Add CSRF Tokens for Critical Operations

For sensitive endpoints:

•Generate unique token per session
•Include token in all state-changing requests
•Validate token on server-side
•Regenerate token after use (optional, for extra security)

Express.js Example:

const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });

// Apply to all POST/PUT/DELETE routes
app.use(csrfProtection);

app.get('/form', (req, res) => {
  res.render('form', { csrfToken: req.csrfToken() });
});

app.post('/transfer', csrfProtection, (req, res) => {
  // Token automatically validated
  // Process transfer...
});

Protect All State-Changing Operations

Always protect:

•POST requests (form submissions)
•PUT requests (updates)
•DELETE requests (deletions)
•PATCH requests (partial updates)

Generally safe (but validate if they change state):

•GET requests (should be idempotent)
•HEAD requests
•OPTIONS requests

Common Mistakes to Avoid

Mistake 1: Only protecting some endpoints

ā€¢āŒ Protecting only sensitive endpoints
ā€¢āœ… Protect ALL endpoints that modify data

Mistake 2: Using GET for state changes

ā€¢āŒ Using GET requests for transfers/updates
ā€¢āœ… Use POST/PUT/DELETE for state changes

Mistake 3: Storing tokens in localStorage

ā€¢āŒ Storing CSRF tokens in localStorage (vulnerable to XSS)
ā€¢āœ… Use httpOnly cookies or include in form

Mistake 4: Not regenerating tokens

ā€¢āŒ Using same token for multiple requests
ā€¢āœ… Consider regenerating after sensitive operations

Key Takeaways

1CSRF tokens are the most reliable prevention method
2SameSite cookie attribute provides additional protection
3Always validate state-changing operations
4Use framework's built-in CSRF protection when available
5Consider SameSite=Strict for sensitive operations