Skip to content
intermediate Phase 13 · Frontend Security

CSRF

Prevent Cross-Site Request Forgery with tokens and SameSite cookies.

30m
0 problems
Topic Progress 0%

CSRF Attacks

CSRF Attacks

How CSRF Works

  1. User logs into bank.com, receives session cookie
  2. User visits malicious site
  3. Malicious site sends request to bank.com
  4. Browser automatically includes session cookie
  5. Bank processes request as if it's from the user

Example Attack

<!-- Malicious site -->
<form action="https://bank.com/transfer" method="POST">
  <input type="hidden" name="to" value="attacker">
  <input type="hidden" name="amount" value="10000">
</form>
<script>document.forms[0].submit();</script>

Vulnerable Code

// Vulnerable: No CSRF protection
app.post('/transfer', (req, res) => {
  // Processes transfer without verifying origin
  const { to, amount } = req.body;
  transferMoney(req.user, to, amount);
  res.json({ success: true });
});

CSRF Targets

  • State-changing operations (POST, PUT, DELETE)
  • Authentication endpoints
  • Financial transactions
  • User settings changes

Safe Methods

  • GET should be safe (no side effects)
  • HEAD, OPTIONS are typically safe
  • POST, PUT, DELETE need CSRF protection

CSRF Protection

CSRF Protection

CSRF Tokens

// Server: Generate token
csrfProtection = require('csurf');
app.use(csrfProtection({ cookie: true }));

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

app.post('/process', (req, res) => {
  // Token is validated automatically
  processForm(req.body);
});

Frontend Token Usage

// React: Include token in forms
function TransferForm() {
  const [token, setToken] = useState('');

  useEffect(() => {
    fetch('/api/csrf-token')
      .then(res => res.json())
      .then(data => setToken(data.token));
  }, []);

  return (
    <form method="POST" action="/transfer">
      <input type="hidden" name="_csrf" value={token} />
      <input name="to" />
      <input name="amount" type="number" />
      <button type="submit">Transfer</button>
    </form>
  );
}

SameSite Cookies

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

// SameSite values:
// strict: Cookie only sent for same-site requests
// lax: Cookie sent for top-level navigation (safe default)
// none: Cookie sent for all requests (requires secure)

Origin/Referer Headers

// Server: Verify origin
app.post('/transfer', (req, res) => {
  const origin = req.headers.origin || req.headers.referer;
  
  if (!origin || !origin.startsWith('https://myapp.com')) {
    return res.status(403).json({ error: 'Invalid origin' });
  }

  // Process request
});

Double Submit Cookie

// Server: Set random token in cookie and require it in header
const csrfToken = crypto.randomBytes(32).toString('hex');
res.cookie('csrf-token', csrfToken, { sameSite: 'strict' });

// Client: Send token in header
fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'X-CSRF-Token': getCookie('csrf-token'),
  },
  body: JSON.stringify(data),
});

Additional Protections

Additional Protections

CORS Configuration

// Server: Configure CORS
const cors = require('cors');

app.use(cors({
  origin: 'https://myapp.com',
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'X-CSRF-Token'],
}));

Custom Headers

// Client: Always send custom header for AJAX
fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'X-Requested-With': 'XMLHttpRequest',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(data),
});

// Server: Verify custom header
app.post('/api/transfer', (req, res) => {
  if (req.headers['x-requested-with'] !== 'XMLHttpRequest') {
    return res.status(403).json({ error: 'Invalid request' });
  }
});

Content-Type Validation

// Server: Only accept specific content types
app.post('/api/transfer', (req, res) => {
  if (req.headers['content-type'] !== 'application/json') {
    return res.status(415).json({ error: 'Unsupported content type' });
  }
});

Frame Protection

// Prevent framing (clickjacking)
app.use((req, res, next) => {
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('Content-Security-Policy', "frame-ancestors 'none'");
  next();
});

Best Practices

  1. Use SameSite cookies (strict or lax)
  2. Implement CSRF tokens for state-changing operations
  3. Validate Origin/Referer headers
  4. Use custom headers for AJAX requests
  5. Don't rely solely on CORS for CSRF protection
  6. Set Content-Type for API requests
  7. Use HTTPS everywhere

Testing CSRF Protection

describe('CSRF Protection', () => {
  it('should reject requests without CSRF token', async () => {
    const response = await request(app)
      .post('/transfer')
      .send({ to: 'attacker', amount: 1000 });

    expect(response.status).toBe(403);
  });

  it('should accept requests with valid CSRF token', async () => {
    const token = await getCsrfToken();
    const response = await request(app)
      .post('/transfer')
      .set('X-CSRF-Token', token)
      .send({ to: 'friend', amount: 100 });

    expect(response.status).toBe(200);
  });
});

Quiz

1. What is CSRF?

Question 1 options

2. What is SameSite cookie attribute?

Question 2 options

3. What is a common mistake when implementing Cross-Site Request Forgery (CSRF)?

Question 3 options

Flashcards

Question

What is CSRF?

Answer

Cross-Site Request Forgery - tricking users into making unintended state-changing requests.

Question

What is SameSite cookie?

Answer

An attribute that controls when cookies are sent with cross-site requests.

Question

How do CSRF tokens work?

Answer

A random token is generated server-side and must be included in requests to verify origin.

Question

Which requests need CSRF protection?

Answer

State-changing requests: POST, PUT, DELETE (not GET which should be safe).

Revision Notes

Key Takeaways

  • 1. CSRF tricks users into making unintended requests
  • 2. Use SameSite cookies as primary defense
  • 3. CSRF tokens protect state-changing operations
  • 4. GET requests should be safe (no side effects)
  • 5. Validate Origin/Referer headers

Interview Tips

  • Explain how CSRF attacks work
  • Discuss SameSite cookie attribute
  • Know multiple CSRF protection methods

Cheat Sheet

CSRF Protection Cheat Sheet

Attack

  • User visits malicious site
  • Malicious site sends request to your app
  • Browser includes session cookie
  • App processes as legitimate request

Protection Methods

  1. CSRF Tokens
  2. SameSite cookies
  3. Origin/Referer validation
  4. Custom headers
  5. CORS configuration

SameSite Values

  • strict: Only same-site
  • lax: Top-level navigation
  • none: All requests (needs secure)

Best Practices

  • Use SameSite: strict or lax
  • CSRF tokens for state changes
  • Validate Origin header
  • Use custom headers for AJAX