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:
-
Use CSRF Tokens
-
Set Up HttpOnly Cookies
-
Apply SameSite Cookie Settings
-
Add Custom Request Headers
-
Check Origin and Referrer Headers
-
Try the Double Submit Cookie Method
-
Manage Sessions Correctly
-
Don't Use GET Requests for Changes
-
Set Up CORS Correctly
-
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.
Related video from YouTube
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:
-
JavaScript can't touch them
-
They keep session tokens safe
-
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.
3. Apply SameSite Cookie Settings
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:
-
Your React app adds a special header to each request.
-
Your server checks for this header.
-
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:
-
Have your server check if these headers match your app's domain.
-
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.
6. Try the Double Submit Cookie Method
The Double Submit Cookie method fights CSRF attacks without server-side token storage. Here's the gist:
-
Server creates a session ID and CSRF token on login
-
Both are sent as cookies to the browser
-
React app includes the CSRF token in every request
-
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:
-
It's a security risk. Attackers can easily mess with GET requests.
-
Browsers and proxies often cache GET requests. This can cause weird behavior.
-
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:
-
Check what's being updated
-
Review changelogs
-
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 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:
-
Use CSRF tokens for every request
-
Generate new tokens on page load
-
Store tokens securely
-
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.