Skip to main content

CSRF, Clickjacking & CORS

Prevent cross-site attacks and control resource sharing

TL;DR

CSRF (Cross-Site Request Forgery): Attacker tricks user into making unintended request (transfer money, change password). Prevent: CSRF tokens, SameSite cookies. Clickjacking: Attacker overlays invisible iframe, tricks user into clicking (button actually clicks attacker's site). Prevent: X-Frame-Options, CSP frame-ancestors. CORS (Cross-Origin Resource Sharing): Control which external sites can access your APIs. Configure headers, validate origin.

Learning Objectives

  • Understand CSRF attack mechanism and prevention
  • Implement CSRF tokens
  • Use SameSite cookies to prevent CSRF
  • Prevent clickjacking with X-Frame-Options and CSP
  • Design CORS policies for APIs

Motivating Scenario

CSRF: User logged into bank.com. Attacker's site has <img src="bank.com/transfer?to=attacker&amount=1000">. User's browser makes request (cookies sent). Bank transfers money.

Solution: Transfer requires CSRF token (unique, in form). Attacker can't forge token. Request fails.

Core Concepts

CSRF Prevention

CSRF Token Approach
  1. Generate unique token per user/session
  2. Include in form/header (unpredictable)
  3. Verify token on server before processing
  4. Token never sent to attacker's site
  5. Recommended for traditional forms
  6. Works across browsers uniformly
SameSite Cookie Approach
  1. Cookie only sent same-site requests
  2. Not automatically sent cross-site
  3. Prevents cookie inclusion by attacker
  4. Simpler, no token management overhead
  5. Modern browser support (2020+)
  6. Complements CSRF tokens (defense-in-depth)

CSRF Attack Flow and Prevention:

CSRF token prevents attacker from forging requests.

Clickjacking: The Invisible Click

Clickjacking works by overlaying an invisible iframe over a visible button. User clicks what they see (e.g., "Download"), but actually clicks a hidden button on attacker's site (e.g., "Approve Transfer").

Visible Layer (user sees):        Hidden Layer (attacker controls):
┌─────────────────────┐ ┌─────────────────────┐
│ [Click to Download] │ │ [Approve Transfer] │
│ │ │ (invisible, opacity:0)
└─────────────────────┘ └─────────────────────┘
↓ User clicks
Invisible iframe's button receives click
Clickjacking: user clicks visible button, but invisible iframe captures click.

X-Frame-Options and CSP Prevention

X-Frame-Options: DENY
→ Browser won't allow your page in any iframe

X-Frame-Options: SAMEORIGIN
→ Browser allows iframe only if same origin

Content-Security-Policy: frame-ancestors 'self'
→ Same as SAMEORIGIN, more flexible, recommended

CORS: Controlling Cross-Origin Access

CORS allows you to explicitly permit cross-origin requests. Without CORS, browser blocks cross-origin requests by default (same-origin policy).

Access-Control-Allow-Origin: https://trusted.com
→ Only trusted.com can fetch from this API

Access-Control-Allow-Origin: *
→ Anyone can fetch (dangerous with credentials)

Access-Control-Allow-Methods: GET, POST, PUT
→ Only these HTTP methods allowed

Access-Control-Allow-Headers: Content-Type, Authorization
→ Only these headers allowed in requests

Access-Control-Allow-Credentials: true
→ Cookies sent with cross-origin requests (be careful!)

Access-Control-Max-Age: 3600
→ Browser can cache preflight response for 1 hour

Practical Examples

// CSRF Token Generation & Verification
const crypto = require('crypto');
const session = require('express-session');

function generateCSRFToken() {
// Generate cryptographically secure random token
return crypto.randomBytes(32).toString('hex');
}

function verifyCSRFToken(req) {
const tokenFromRequest = req.body.csrf_token || req.headers['x-csrf-token'];
const tokenFromSession = req.session.csrfToken;

if (!tokenFromRequest || !tokenFromSession) {
return false;
}

// Constant-time comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(tokenFromRequest),
Buffer.from(tokenFromSession)
);
}

// Middleware: require CSRF token on state-changing requests
app.post('/transfer', (req, res, next) => {
if (!verifyCSRFToken(req)) {
return res.status(403).json({ error: 'CSRF token invalid' });
}
// Token verified, safe to process
processTransfer(req.body);
res.json({ success: true });
});

// GET request: generate and return token to client
app.get('/form', (req, res) => {
const token = generateCSRFToken();
req.session.csrfToken = token;
res.render('transfer-form', { csrfToken: token });
});

// Client-side form
// <form method="POST" action="/transfer">
// <input type="hidden" name="csrf_token" value="<%= csrfToken %>">
// <input type="text" name="amount" required>
// <button type="submit">Transfer</button>
// </form>

Patterns and Pitfalls

Token generated but never validated on server. Attacker's forged request succeeds.
Middleware validates token before processing state-changing requests. Different token per session, rotated after login.
Lax allows token in some cross-site contexts (top-level navigation). May not protect from all attacks.
Strict prevents cookie on all cross-site requests. Requires token for sensitive operations. Defense-in-depth.
Access-Control-Allow-Origin: * with credentials=true. Anyone can access your API.
Explicitly whitelist trusted origins. Use origin validation function. Test before adding new origins.

Self-Check

  • What's the difference between SameSite=Strict and SameSite=Lax?
  • Why can't attackers forge CSRF tokens?
  • What is a preflight request, and when does the browser send it?
  • When would you use CORS credentials=true, and what are the risks?
  • How does X-Frame-Options differ from CSP frame-ancestors?

Design Review Checklist

  • CSRF tokens on all state-changing requests (POST, PUT, DELETE)?
  • CSRF tokens generated cryptographically (not predictable)?
  • Token validation uses constant-time comparison?
  • Tokens rotated after user login?
  • SameSite cookies set to Strict or Lax?
  • X-Frame-Options header present (DENY or SAMEORIGIN)?
  • CSP frame-ancestors configured?
  • CORS origins explicitly whitelisted (no wildcard)?
  • CORS credentials=true only when necessary?
  • Preflight requests (OPTIONS) handled?
  • CORS headers validated on server?
  • Tests cover CSRF and CORS behavior?
  • Developers trained on cross-site attack vectors?
  • Security headers tested in all browsers?

Next Steps

  1. Audit current implementation — Check for CSRF tokens, SameSite cookies, X-Frame-Options
  2. Implement defense-in-depth — Use CSRF tokens + SameSite + CSP
  3. Configure CORS carefully — Whitelist origins, validate requests
  4. Test cross-site scenarios — Verify tokens can't be forged, CORS blocks unauthorized origins
  5. Monitor for attacks — Log CSRF rejections, CORS violations

References