Codebrahma

Work

React CSRF Protection: 10 Best Practices

React doesn't come with built-in CSRF protection, leaving your app vulnerable. Here are 10 ways to shield your React apps from CSRF attacks:

  1. Use CSRF Tokens

  2. Set Up HttpOnly Cookies

  3. Apply SameSite Cookie Settings

  4. Add Custom Request Headers

  5. Check Origin and Referrer Headers

  6. Try the Double Submit Cookie Method

  7. Manage Sessions Correctly

  8. Don't Use GET Requests for Changes

  9. Set Up CORS Correctly

  10. Do Regular Security Checks and Updates

Quick Comparison:

Method Pros Cons
CSRF Tokens Very secure, works with most systems Needs server setup
SameSite Cookies Easy to set up Old browsers might not support
Custom Headers Simple for AJAX Doesn't work for forms
Origin/Referrer Checks Helps verify request sources Can be spoofed
Double Submit Cookie Adds extra layer of protection Slightly more complex

Remember: No single method is perfect. Use a mix of these for the best protection against CSRF attacks in your React app.

Use CSRF Tokens

CSRF tokens are your best friend against Cross-Site Request Forgery attacks in React apps. They're like a secret handshake between your app and the server.

Here's how to set them up:

1. Generate the token

Use a server-side library like csurf to create tokens. Set up an endpoint:

const csrfProtection = csrf({ cookie: true });
app.use(csrfProtection);
app.get('/getCSRFToken', (req, res) => {
    res.json({ CSRFToken: req.CSRFToken() });
});

2. Grab the token

In your React component, fetch the token when it mounts:

const getCSRFToken = async () => {
    const response = await axios.get('/getCSRFToken');
    axios.defaults.headers.post['X-CSRF-Token'] = response.data.CSRFToken;
};

useEffect(() => {
    getCSRFToken();
}, []);

3. Use the token

Include it in your POST request headers:

axios.post('/api/data', data, {
    headers: {
        'X-CSRF-Token': csrfToken
    }
});

4. Check the token

On the server, make sure the received token matches:

if (hash_equals($_SESSION['token'], $_POST['token'])) {
    // All good, process the request
} else {
    // Nope, reject it
}

A few tips:

  • Make your tokens big and random

  • Don't let them hang around too long

  • Keep them out of GET requests and URLs

  • Always use HTTPS

2. Set Up HttpOnly Cookies

HttpOnly cookies are your secret weapon against cross-site scripting (XSS) attacks. They block client-side scripts from accessing cookie data. Here's why they're awesome:

  1. JavaScript can't touch them

  2. They keep session tokens safe

  3. They shrink the XSS attack surface

Setting up HttpOnly cookies in React? It's a piece of cake. Check this out:

Server-side (Express.js):

const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();

app.use(cookieParser());

app.get('/setCookie', (req, res) => {
    res.cookie('sessionId', 'your-session-value', { httpOnly: true });
    res.send('Cookie set with HttpOnly flag');
});

app.listen(3000);

React component:

import React, { useEffect } from 'react';
import axios from 'axios';

function App() {
    useEffect(() => {
        axios.get('http://localhost:3000/setCookie', { withCredentials: true });
    }, []);

    // Rest of your component
}

Don't forget: Set withCredentials: true in your API calls. It keeps the session alive and blocks unauthorized access.

Quick tips:

  • Use strong encryption (AES-256) for server-side key/value pairs

  • Set short cookie expiration dates

  • Monitor cookie activity

  • Use HttpOnly cookies only over HTTPS

  • Avoid weak encryption (MD5, SHA-1)

Remember: HttpOnly cookies aren't bulletproof. Pair them with other security measures like user authentication and access control policies.

SameSite cookies are your secret weapon against CSRF attacks. They control when browsers send cookies with cross-site requests.

Here's the lowdown on SameSite values:

1. Strict: Cookies stay put. Only sent for same-site requests.

2. Lax: A bit more flexible. Sent for same-site and top-level navigation.

3. None: Free-for-all. Sent everywhere, but must be marked Secure.

Check out how different requests play with SameSite settings:

Request Type Example Cookies Sent
Link <a href="..."> Normal, Lax
Form GET <form method="GET" action="..."> Normal, Lax
Form POST <form method="POST" action="..."> Normal
iframe <iframe src="..."></iframe> Normal
AJAX $.get("...") Normal
Image <img src="..."> Normal

Want to set SameSite cookies in your React app? You'll need to do it server-side. Here's a quick Express.js example:

app.use(session({
  cookie: {
    sameSite: 'strict',
    secure: true
  }
}));

This sets the cookie to 'strict' and marks it secure.

Remember:

  • 'Strict' for cookies that should never leave your site.

  • 'Lax' is a solid default.

  • If you use 'None', always pair it with 'Secure'.

4. Add Custom Request Headers

Custom request headers beef up your CSRF protection. Here's how:

  1. Your React app adds a special header to each request.

  2. Your server checks for this header.

  3. No header? Request denied.

It's that simple. And it works because other websites can't add custom headers to cross-origin requests. That's just how browsers roll.

Here's how to do it in React:

// Axios
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

// fetch
fetch(url, {
  headers: {
    'X-Requested-With': 'XMLHttpRequest'
  }
})

On the server (using Express):

app.use((req, res, next) => {
  if (req.headers['x-requested-with'] !== 'XMLHttpRequest') {
    return res.status(403).json({ error: 'CSRF token missing' });
  }
  next();
});

Want to level up? Combine custom headers with CSRF tokens:

// React component
const [csrfToken, setCsrfToken] = useState('');

useEffect(() => {
  const fetchCsrfToken = async () => {
    const response = await axios.get('/api/csrf-token');
    setCsrfToken(response.data.csrfToken);
  };
  fetchCsrfToken();
}, []);

// Making a request
await axios.post('/api/submit-form', formData, {
  headers: {
    'X-CSRF-Token': csrfToken,
  },
});

This combo packs a punch against CSRF attacks. It's like having a bouncer AND a guest list at your app's door.

A few tips:

  • Use custom headers for POST, PUT, DELETE requests.

  • Layer up: combine this with other CSRF defenses.

  • Stick to one custom header name across your app.

5. Check Origin and Referrer Headers

Origin and Referrer headers are your friends in the fight against CSRF attacks. Here's why:

These headers tell you where a request came from. The browser sets them, and JavaScript can't mess with them. That makes them tough for attackers to fake.

How to use them:

  1. Have your server check if these headers match your app's domain.

  2. Make a whitelist of allowed origins and check against it.

Here's a simple Express middleware example:

const allowedOrigins = ['https://yourapp.com', 'https://www.yourapp.com'];

app.use((req, res, next) => {
  const origin = req.headers.origin;
  const referer = req.headers.referer;

  if (!origin && !referer) {
    return res.status(403).json({ error: 'Origin or Referer required' });
  }

  if (origin && !allowedOrigins.includes(origin)) {
    return res.status(403).json({ error: 'Invalid Origin' });
  }

  if (referer && !allowedOrigins.some(allowed => referer.startsWith(allowed))) {
    return res.status(403).json({ error: 'Invalid Referer' });
  }

  next();
});

What to remember:

  • Check both headers when you can.

  • Be strict about matching origins.

  • Don't let requests through if headers are missing.

A real developer used this for a newsletter signup form. They only allowed submissions from their static site and signup app URLs. Smart move.

"I whitelisted 'https://brandur.org' and 'https://passages-signup.herokuapp.com'. It worked like a charm." - Anonymous Developer

This method is great, but it's not perfect. OWASP still says to use token-based CSRF protection as your main defense. So, mix and match for the best security.

The Double Submit Cookie method fights CSRF attacks without server-side token storage. Here's the gist:

  1. Server creates a session ID and CSRF token on login

  2. Both are sent as cookies to the browser

  3. React app includes the CSRF token in every request

  4. Server checks if the tokens match

Here's a quick Express and React example:

Express server:

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

app.use(csrfProtection);

app.get('/getCSRFToken', (req, res) => {
    res.json({ CSRFToken: req.csrfToken() });
});

React app:

import axios from 'axios';

const getCSRFToken = async () => {
    const response = await axios.get('/getCSRFToken');
    axios.defaults.headers.post['X-CSRF-Token'] = response.data.CSRFToken;
};

useEffect(() => {
    getCSRFToken();
}, []);

This ensures every POST request includes the CSRF token.

But there's a catch: if an attacker can set cookies, they might bypass this protection. The fix? Use a signed token:

const crypto = require('crypto');

function generateToken(sessionId, secret) {
    const message = `${sessionId}!${crypto.randomBytes(32).toString('hex')}`;
    const hmac = crypto.createHmac('sha256', secret).update(message).digest('hex');
    return `${hmac}.${message}`;
}

This ties the token to the user's session, making it tougher to fake.

Key points:

  • Skip the HttpOnly flag for your CSRF cookie

  • Always use HTTPS

  • Update tokens regularly

sbb-itb-cc15ae4

7. Manage Sessions Correctly

Good session management is crucial for CSRF protection in React apps. Here's how to do it:

Use HttpOnly cookies for session IDs. This blocks JavaScript access, making cookie theft harder.

Set short expiration times. Long sessions are risky. Here's an example:

const express = require('express');
const session = require('express-session');

const app = express();

app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: true,
  cookie: { secure: true, httpOnly: true, maxAge: 1800000 } // 30 minutes
}));

Regenerate session IDs on login or privilege changes:

app.post('/login', (req, res) => {
  // ... authenticate user ...
  req.session.regenerate((err) => {
    if (err) next(err);
    // ... save user info in new session ...
    res.redirect('/');
  });
});

Use secure storage in production. Redis is a good option:

const redis = require('redis');
const connectRedis = require('connect-redis');

const RedisStore = connectRedis(session);
const redisClient = redis.createClient();

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: true,
  cookie: { secure: true, httpOnly: true, maxAge: 1800000 }
}));

Implement idle timeout with react-idle-timer:

import React from 'react';
import IdleTimer from 'react-idle-timer';

function App() {
  const handleOnIdle = () => {
    console.log('User is idle');
    // Implement logout logic here
  }

  return (
    <IdleTimer
      timeout={1000 * 60 * 15} // 15 minutes
      onIdle={handleOnIdle}
    >
      {/* Your app content */}
    </IdleTimer>
  );
}

These steps will help keep your React app's sessions secure.

8. Don't Use GET Requests for Changes

GET requests? Great for fetching data. For changing stuff on your server? Not so much. Here's why:

  1. It's a security risk. Attackers can easily mess with GET requests.

  2. Browsers and proxies often cache GET requests. This can cause weird behavior.

  3. It makes your site more vulnerable to Cross-Site Request Forgery attacks.

So what should you use instead? POST, PUT, or DELETE. Here's a quick breakdown:

HTTP Method Use Case Example
GET Retrieve data Fetch user profile
POST Create new data Add a new blog post
PUT Update existing data Edit user details
DELETE Remove data Delete a comment

Here's how to do it right in React:

// Don't do this
const deleteUser = (userId) => {
  fetch(`/api/users/${userId}`, { method: 'GET' });
};

// Do this instead
const deleteUser = (userId) => {
  fetch(`/api/users/${userId}`, { method: 'DELETE' });
};

Using RESTful principles makes your API more secure and easier to use. It's a win-win.

"Using GET requests for state-changing actions is like leaving your front door wide open. It's an invitation for trouble", says Sarah Drasner, VP of Developer Experience at Netlify.

9. Set Up CORS Correctly

CORS (Cross-Origin Resource Sharing) is a big deal for web security. It's all about controlling how your React app talks to servers on different domains. Get it wrong, and you're leaving the door open for CSRF attacks.

Here's the lowdown on setting up CORS:

1. Lock down those origins

Don't use the wildcard (*) for Access-Control-Allow-Origin. That's like giving everyone a key to your house. Instead, list out the domains you trust:

const allowedOrigins = ['https://yourapp.com', 'https://api.yourapp.com'];
app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (allowedOrigins.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
  }
  next();
});

2. Specify allowed methods

Only let in the HTTP methods your API actually needs:

res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');

3. Use a proxy for local dev

Bypass CORS issues during development with a proxy. In your package.json:

{
  "proxy": "http://localhost:5000"
}

4. Lock down those cookies

Make your cookies as secure as Fort Knox:

res.cookie('sessionId', 'abc123', { 
  httpOnly: true, 
  secure: true, 
  sameSite: 'strict' 
});

5. Handle preflight requests

Your server needs to play nice with OPTIONS requests:

app.options('*', (req, res) => {
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.sendStatus(200);
});

Remember: CORS is a server-side thing. Your React app isn't in charge here - it's all about how you set up your backend.

"CORS is like a bouncer for your web app. It checks IDs and decides who gets in. Set it up right, and you'll keep the troublemakers out while letting your VIP users through", says Dan Abramov, co-author of Redux and Create React App.

10. Do Regular Security Checks and Updates

Keeping your React app safe from CSRF attacks is an ongoing process. Here's why regular checks and updates matter:

1. Patch security holes

New vulnerabilities pop up constantly. Updating your dependencies fixes these issues before attackers can exploit them.

2. Stay ahead of attackers

Hackers always cook up new tricks. Regular checks help you spot and fix problems early.

3. Improve app performance

Updates often boost performance, giving users a faster, more stable app.

How to stay on top of security:

Use automated tools

Renovate and Dependabot can manage your dependencies. They'll alert you about updates and create pull requests automatically.

Run security scans

Snyk or OWASP ZAP can catch vulnerabilities that humans might miss.

Set a schedule

Don't leave updates to chance. Check and update dependencies monthly.

Here's a simple checklist:

Task Frequency Tools
Update dependencies Monthly npm-outdated, yarn upgrade
Run security scans Weekly Snyk, OWASP ZAP
Review changelogs Before updates GitHub, npm
Test after updates After updates Jest, React Testing Library

Updating isn't just npm update. You need to:

  1. Check what's being updated

  2. Review changelogs

  3. Test your app thoroughly

René Gielen, VP of Apache Struts, warns:

"Most breaches we become aware of are caused by failure to update software components that are known to be vulnerable for months or even years."

Don't let your app become a statistic. Stay vigilant, stay updated, and keep CSRF attackers at bay.

Wrap-up

CSRF attacks can mess up your React app. But don't worry - you've got ways to stop them. Here's what you need to know:

1. CSRF tokens

These are your first defense. Add them to forms and check them on the server.

2. HttpOnly cookies

Keep important data safe from client-side scripts.

3. SameSite cookie settings

Control how cookies work across different sites.

4. Custom headers

Add extra checks to your requests.

5. Origin and Referrer checks

Make sure requests are coming from the right place.

No single method is perfect. Use a mix of these for the best protection.

Here's a quick look at some key CSRF protection methods:

Method Good Not So Good
CSRF Tokens Very secure, works with most systems Needs server setup
SameSite Cookies Easy to set up, works with browsers Old browsers might not support it
Custom Headers Simple for AJAX Doesn't work for forms

Auth0, a big name in authentication, says:

"The goal is to explain how CSRF attacks work and provide you with the basic principles to protect your web application."

By using these methods, you're not just checking boxes. You're building a wall around your React app to keep users and data safe.

Stay alert and keep learning. Assume attackers are always one step ahead. With these tools, you're ready to fight off CSRF attacks and keep your React app running smoothly.

Quick Checklist

Here's a rundown of the 10 best practices for React CSRF protection:

1. Use CSRF Tokens

Add these to forms and check them server-side. They're your first line of defense.

2. Set Up HttpOnly Cookies

Keep sensitive data out of reach from client-side scripts. It's like putting your valuables in a safe.

3. Apply SameSite Cookie Settings

Control how cookies behave across sites. Think of it as setting boundaries for your cookies.

4. Add Custom Request Headers

Include extra checks in your requests. It's like adding a secret handshake.

5. Check Origin and Referrer Headers

Make sure requests are coming from where they say they are. Don't trust strangers, right?

6. Try Double Submit Cookie Method

Add an extra layer of protection. It's like using both a lock and a deadbolt.

7. Manage Sessions Correctly

Prevent session fixation and hijacking. Keep your users' sessions safe and sound.

8. Don't Use GET Requests for Changes

Stick to POST and DELETE for state changes. GET requests are too easy to fake.

9. Set Up CORS Correctly

Control which domains can access your resources. It's like having a bouncer at the door.

10. Do Regular Security Checks and Updates

Stay on top of new threats. The bad guys don't rest, so neither should you.

Practice Why It Matters
CSRF Tokens Proves requests are from real users
HttpOnly Cookies Keeps cookie data away from sneaky scripts
SameSite Settings Cuts down on cross-site request risks
Custom Headers Helps spot fake requests
Origin/Referrer Checks Makes sure requests are from trusted sources
Double Submit Cookie Adds another layer of checking
Proper Session Management Reduces chances of session attacks
Avoid GET for Changes Lowers risk when changing app state
Correct CORS Setup Controls who can talk to your app
Regular Security Updates Keeps your defenses up-to-date

FAQs

Does React prevent CSRF?

React

React doesn't prevent CSRF attacks. It's for building UIs, not security. You need to add protection yourself.

How does React handle CSRF?

React doesn't handle CSRF. You set up security measures. Here's how:

  1. Use CSRF tokens for every request

  2. Generate new tokens on page load

  3. Store tokens securely

  4. Send tokens in headers

Example:

function ProfilePage() {
  const csrfToken = getCsrfToken(); // Get token from secure storage

  const updateProfile = async (data) => {
    await fetch('/api/profile', {
      method: 'POST',
      headers: {
        'X-CSRF-Token': csrfToken
      },
      body: JSON.stringify(data)
    });
  };

  return <form onSubmit={updateProfile}>...</form>;
}

"This CSRF token is sent alongside every request, and it generates every time your profile page loads." - Siddhant Varma, Full Stack JavaScript Developer

CSRF tokens aren't enough. You need other security steps like proper cookie settings and checking request origins.

Written by
Anand Narayan
Published at
Oct 04, 2024
Posted in
Web Security
Tags
If you want to get more posts like this, join our newsletter

Join our NEW newsletter to learn about the latest trends in the fast changing front end atmosphere

Mail hello@codebrahma.com

Phone +1 484 506 0634

Codebrahma is an independent company. Mentioned brands and companies are trademarked brands.
© 2024 codebrahma.com. All rights reserved.