AI Trailblazer Award

Insights

CSRF Tokens in React: When You Actually Need Them

Last updated: July 9, 202612 mins read
CSRF Tokens in React: When You Actually Need Them

You’ve built an awesome React SPA and are getting ready to deploy. But wait—should you implement CSRF protection? You search online and find conflicting advice. Some tutorials from “pro React developers” don’t even mention CSRF tokens, while others claim they’re absolutely essential. What’s the truth?

The confusion around CSRF protection in React applications is widespread and understandable. In this article, we’ll clear up when you actually need CSRF tokens and when you don’t—because the answer isn’t always “yes.”

What is Cross-Site Request Forgery (CSRF)? A 60-Second Refresher

Cross-Site Request Forgery (CSRF) is an attack that tricks an authenticated user’s browser into performing unwanted actions on a website where they’re currently logged in. The key vulnerability is that browsers automatically include cookies (including authentication cookies) with requests to a domain.

For example, imagine you’re logged into your banking website in one tab. In another tab, you visit a malicious site that contains code to submit a form to your bank’s transfer endpoint. When this form submits, your browser will automatically include your banking session cookies with the request. Without CSRF protection, the bank’s server has no way to tell that this request came from the malicious site instead of the legitimate banking interface.

The impact can be severe: unauthorized fund transfers, password changes, data theft, or account takeovers—all exploiting the user’s authenticated session.

The Deciding Factor: How You Handle Authentication

The necessity of CSRF protection in your React application depends almost entirely on how you implement authentication. Let’s break down the two primary scenarios:

If your React application uses cookies for authentication (session cookies or even JWTs stored in cookies), you are vulnerable to CSRF attacks and need protection.

The “Why”: When using cookie-based authentication, browsers automatically send cookies with every request to the domain that set them. This automatic cookie inclusion happens regardless of where the request originated from (your app or a malicious site), making it impossible for your server to distinguish legitimate requests from forged ones based on cookies alone.

This vulnerability exists because of how web browsers handle cookies and the same-origin policy—they attach relevant cookies to requests even when those requests are initiated by third-party sites.

The Solution: CSRF Tokens

CSRF tokens provide a layer of verification that the request came from your frontend, not a third party. Here’s how they typically work:

  1. The server generates a unique, unpredictable token when a user logs in or starts a session
  2. This token is made available to the client-side JavaScript (but not to third-party sites)
  3. For each state-changing request (POST, PUT, DELETE), your frontend code must include this token
  4. The server validates the token before processing the request

Common implementation patterns include:

Synchronizer Token Pattern: The server generates a token per session and provides it via an API endpoint. The client includes this token in a custom header with each state-changing request.

Double Submit Cookie Pattern: The server sets a CSRF token in a cookie that is not HttpOnly (so JavaScript can read it). Your React code reads this token from the cookie and sends it back in a custom HTTP header (e.g., X-CSRF-Token). The server verifies that the token in the header matches the token in the cookie.

Scenario 2: You Likely DON’T Need CSRF Tokens (Token-Based Authentication)

If your React application uses token-based authentication with JWTs or similar tokens sent in the Authorization header (not in cookies), you generally don’t need CSRF protection.

The “Why”: With token-based authentication, your frontend code must explicitly retrieve the token (from memory or storage) and include it in the Authorization header of each request. Browsers do NOT automatically attach this header to cross-site requests, which means a malicious site cannot forge a properly authenticated request.

In this model, your client-side code is responsible for:

  1. Obtaining the token (usually during login)
  2. Storing it securely (in memory or potentially localStorage/sessionStorage)
  3. Explicitly including it in the Authorization header of API requests

A third-party site cannot access your token due to the same-origin policy that prevents JavaScript on one domain from accessing data on another domain.

The Critical Caveat: JWTs in Cookies

Here’s where many developers get confused: If you decide to store your JWT in a cookie (even an HttpOnly cookie for XSS protection), you’ve reintroduced the CSRF vulnerability. This is because the browser will automatically send that cookie with requests to your domain, just like a session cookie.

In this specific case, you must implement CSRF protection, typically using the Double Submit Cookie pattern described above.

Let’s look at how to implement CSRF protection in a React app that needs it (using cookie-based authentication):

Step 1: Server-Side Setup (Node.js/Express Example)

First, let’s set up the server to generate and validate CSRF tokens:

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

app.use(cookieParser());
app.use(express.json());

// Generate and set CSRF token on authentication or initial page load
app.post('/api/login', (req, res) => {
  // Authenticate user (omitted for brevity)
  // ...

  // Generate CSRF token
  const csrfToken = crypto.randomBytes(16).toString('hex');
  
  // Set authentication cookie (could be a session ID or JWT)
  res.cookie('AUTH_COOKIE', 'user-auth-data', { 
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax' // Adds some CSRF protection for modern browsers
  });
  
  // Set CSRF token in a readable cookie (not HttpOnly)
  res.cookie('XSRF-TOKEN', csrfToken, { 
    httpOnly: false,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax'
  });
  
  res.json({ success: true, user: { /* user data */ } });
});

// CSRF protection middleware for state-changing methods
function validateCsrfToken(req, res, next) {
  const methodsToProtect = ['POST', 'PUT', 'DELETE', 'PATCH'];
  
  if (methodsToProtect.includes(req.method)) {
    const tokenFromHeader = req.headers['x-xsrf-token'];
    const tokenFromCookie = req.cookies['XSRF-TOKEN'];
    
    if (!tokenFromHeader || !tokenFromCookie || tokenFromHeader !== tokenFromCookie) {
      return res.status(403).json({ error: 'Invalid CSRF token' });
    }
  }
  
  next();
}

// Apply CSRF protection to API routes
app.use('/api', validateCsrfToken);

// Protected endpoint example
app.post('/api/update-profile', (req, res) => {
  // This route is now protected from CSRF
  // Process the update...
  res.json({ success: true });
});

Step 2: Client-Side Setup (React with Axios)

Now, let’s configure our React application to include the CSRF token with each request:

import axios from 'axios';
import Cookies from 'js-cookie';

// Create an Axios instance with default config
const apiClient = axios.create({
  baseURL: 'https://api.example.com',
  withCredentials: true // Important! Allows cookies to be sent with requests
});

// Add request interceptor to include CSRF token
apiClient.interceptors.request.use(
  config => {
    // Read the CSRF token from the cookie
    const csrfToken = Cookies.get('XSRF-TOKEN');
    
    if (csrfToken) {
      // Add it as a header to each request
      config.headers['X-XSRF-TOKEN'] = csrfToken;
    }
    
    return config;
  },
  error => Promise.reject(error)
);

// Example React component making a protected request
function ProfileEditor() {
  const updateProfile = async (profileData) => {
    try {
      // The CSRF token will be automatically included
      const response = await apiClient.post('/api/update-profile', profileData);
      console.log('Profile updated!', response.data);
    } catch (error) {
      console.error('Error updating profile:', error);
    }
  };
  
  // Component JSX...
}

The withCredentials: true setting is crucial for cross-domain requests, as it instructs the browser to include cookies when making requests to a different origin. This is necessary for CORS scenarios where your API might be on a different domain than your React app.

Addressing Nuances and Modern Defenses

What about SameSite Cookies?

Modern browsers support the SameSite cookie attribute, which provides significant CSRF protection when properly configured:

  • SameSite=Strict: Cookies are only sent in same-site requests
  • SameSite=Lax (default in most browsers): Cookies are sent in same-site requests and top-level navigations using safe HTTP methods (like GET)
  • SameSite=None: Cookies are sent in all contexts (requires Secure attribute)

While SameSite=Lax offers substantial protection against CSRF attacks, it’s not a complete solution:

  1. It doesn’t protect against attacks using GET requests (if they’re state-changing, which is bad practice but happens)
  2. Older browsers may not support this attribute, leaving some users vulnerable
  3. Some complex applications need cross-site functionality that Strict would break

For these reasons, implementing CSRF tokens alongside SameSite cookies provides defense-in-depth protection.

CSRF vs. XSS (Cross-Site Scripting)

It’s important to understand the relationship between CSRF and XSS:

  • CSRF tricks the user’s browser into making unwanted requests with their credentials
  • XSS injects malicious scripts into your website that run in the user’s browser

A successful XSS attack can often defeat CSRF protection. If an attacker can run JavaScript on your site’s origin, they can read CSRF tokens from non-HttpOnly cookies or intercept them from API responses, then use those tokens to make authenticated requests.

This highlights the importance of protecting against both vulnerabilities:

  1. Prevent XSS by avoiding dangerouslySetInnerHTML, validating user input, and implementing Content Security Policy (CSP)
  2. Implement CSRF tokens for cookie-based authentication
  3. Consider using HttpOnly cookies for sensitive data to prevent JavaScript access (but remember this requires CSRF protection)

Defense in depth is key to web security. Each layer provides protection if another fails.

A Clear Decision Framework

To summarize when you need CSRF protection in your React application:

You NEED CSRF Protection When:

  • You use cookies for authentication (session cookies or JWTs in cookies)
  • You implement “Remember Me” functionality with persistent cookies
  • Your API endpoints process requests with automatically attached cookies

You DON’T Need CSRF Protection When:

  • You use token-based authentication with tokens stored in memory
  • Your authentication tokens are sent exclusively via the Authorization header
  • You manually attach authentication with each request (not using cookies)

Here’s a simple decision tree:

  1. Are you using cookies for authentication?
    • Yes: You need CSRF protection. Implement the Double Submit Cookie pattern.
    • No: Continue to next question.
  2. How are you storing authentication tokens?
    • In memory or localStorage/sessionStorage (sent via Authorization header): No CSRF protection needed.
    • In cookies: You need CSRF protection.

Practical Security Recommendations

Beyond CSRF, here are some additional security best practices for your React applications:

  1. Protect against XSS:
    • Avoid using dangerouslySetInnerHTML unless absolutely necessary
    • Validate user input on both frontend and backend
    • Implement proper Content Security Policy headers
    • Use environment variables for sensitive configuration, not source code
  2. Secure your authentication:
    • Use HTTPS with proper SSL configuration
    • Set appropriate cookie flags (Secure, HttpOnly, SameSite)
    • Consider using a key vault service for storing secrets rather than environment variables
    • Implement proper token expiration and rotation
  3. Implement proper API security:
    • Validate all user-supplied values on the backend, never trust frontend validation alone
    • Return generic error messages that don’t reveal implementation details
    • Use proper CORS configuration to restrict which origins can access your API
    • Consider implementing rate limiting to prevent abuse
  4. Protect your source code:
    • In production, use minification and disable source maps (or keep them private)
    • Don’t store sensitive data or API keys in your React code
    • Use sandboxing techniques when rendering user-generated content

Conclusion

The confusion around CSRF protection in React applications is understandable, especially with conflicting advice online. The key takeaway is that CSRF protection is not universally required—it depends on your authentication mechanism.

If you use cookie-based authentication, you need CSRF protection. If you use token-based authentication with tokens sent via the Authorization header, you generally don’t.

Understanding the underlying authentication flow is crucial to implementing the right security measures. Don’t blindly follow tutorials without understanding why certain security measures are (or aren’t) needed for your specific architecture.

Modern browsers provide some built-in protections like SameSite cookies, but a defense-in-depth approach is always best practice for applications handling sensitive data or actions.

By making informed decisions about your security architecture, you can protect your users without implementing unnecessary complexity—and that’s a win for both security and developer experience.

Frequently Asked Questions

When do I absolutely need CSRF protection in a React app?

You absolutely need CSRF protection if your application uses cookies for authentication. This includes traditional session cookies or if you choose to store JWTs in cookies. Because browsers automatically send cookies with every request to your domain, a malicious site can exploit this behavior. If you use token-based authentication where the token is sent in an Authorization header, you generally do not need separate CSRF protection.

Why don’t I need CSRF tokens with JWTs in the Authorization header?

You don’t need CSRF tokens in this scenario because, unlike cookies, browsers do not automatically attach the Authorization header to cross-site requests. A malicious website cannot force a user’s browser to send your API a request with the correct Authorization header. Your client-side JavaScript code must explicitly retrieve the token (from memory or local storage) and attach it to the request, and this manual step is what breaks the CSRF attack vector.

Is setting SameSite=Lax on my cookies enough for CSRF protection?

No, relying solely on SameSite cookies is not a complete solution, although it provides significant protection. The SameSite attribute is a powerful first line of defense and is enabled by default in modern browsers. However, it may not be supported by older browsers, and certain types of attacks can still bypass it. For robust security, especially in applications handling sensitive data, you should use CSRF tokens as an additional layer of protection in a defense-in-depth strategy.

What is the most common way to implement CSRF protection in React?

The most common and recommended method for a React SPA using cookie-based authentication is the Double Submit Cookie pattern. In this pattern, the server sets two cookies: an HttpOnly authentication cookie and a readable CSRF token cookie. Your React application reads the CSRF token from its cookie and sends it back in a custom HTTP header (e.g., X-CSRF-Token) with every state-changing request. The server then verifies that the token in the header matches the one in the cookie.

How is a CSRF attack different from an XSS attack?

A CSRF (Cross-Site Request Forgery) attack tricks a user’s browser into making an unwanted request, while an XSS (Cross-Site Scripting) attack injects malicious code that runs in the user’s browser. In short, CSRF exploits the trust a server has for a user’s browser by hijacking its authenticated session. XSS exploits the trust a user has for your website by running malicious scripts. A successful XSS attack can often be used to defeat CSRF protection, which is why it’s crucial to protect against both.

If I store my JWT in localStorage, isn’t it vulnerable to XSS?

Yes, storing JWTs in localStorage or sessionStorage makes them accessible to JavaScript, and therefore vulnerable if an XSS attack occurs. This is a key security trade-off: storing tokens in localStorage protects you from CSRF but exposes you more to XSS. Storing them in HttpOnly cookies protects from XSS but exposes you to CSRF (requiring token protection). This is why a comprehensive security strategy includes robust XSS prevention, such as input sanitization and a Content Security Policy, regardless of where you store your tokens.

Further Resources

Related Articles