CSRF Prevention in React.js: A Practical Guide

Why CSRF Still Breaks React Apps

Cross-Site Request Forgery (CSRF) tricks a logged-in user’s browser into sending unwanted requests. In React.js, the fix is simple: use anti-CSRF tokens + SameSite cookies + “credentialed” requests. This post shows working patterns you can paste in today. React protects the UI, not your cookies. If your session cookie is sent automatically, a malicious site can trigger state-changing requests on your behalf. Let’s fix that—with code.

CSRF Prevention in React.js: A Practical Guide

Core Defenses (Quick Wins)

  • Use SameSite cookies + Secure + HttpOnly.

  • Require an anti-CSRF token on every state-changing request.

  • Enforce CORS and origin/referrer checks server-side.

For more articles, see the Pentest Testing Corp blog: https://www.pentesttesting.com/blog/

1) Server: issue a CSRF token (Express + csurf)

// server.js
import express from "express";
import cookieParser from "cookie-parser";
import csrf from "csurf";

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

// store token in cookie; validate header "X-CSRF-Token"
app.use(csrf({ cookie: { httpOnly: true, sameSite: "lax", secure: true } }));

app.get("/csrf-token", (req, res) => {
  res.json({ csrfToken: req.csrfToken() });
});

app.post("/api/transfer", (req, res) => {
  // your protected action
  res.json({ ok: true });
});

app.listen(3000);

2) React: bootstrap axios with the token

// api.js
import axios from "axios";
axios.defaults.withCredentials = true;

export async function initCsrf() {
  const { data } = await axios.get("/csrf-token");
  axios.defaults.headers.common["X-CSRF-Token"] = data.csrfToken;
}
// App.jsx
import { useEffect } from "react";
import { initCsrf } from "./api";
import axios from "axios";

export default function App() {
  useEffect(() => { initCsrf(); }, []);

  const transfer = async () => {
    await axios.post("/api/transfer", { amount: 100 });
    alert("done");
  };

  return <button onClick={transfer}>Send $100</button>;
}

🖼 Screenshot of our free Website Vulnerability Scanner homepage

Screenshot of the free tools webpage where you can access security assessment tools.
Screenshot of the free tools webpage where you can access security assessment tools.

3) Using native fetch (no axios)

// send with credentials and header
async function securePost(url, body, token) {
  return fetch(url, {
    method: "POST",
    credentials: "include",
    headers: {
      "Content-Type": "application/json",
      "X-CSRF-Token": token
    },
    body: JSON.stringify(body)
  });
}

4) Set session cookie flags

// when you create the session cookie
res.cookie("sid", sid, {
  httpOnly: true,
  secure: true,        // on HTTPS
  sameSite: "lax"      // or "strict" if UX allows
});

5) Alternative: double-submit cookie pattern

// server: mint token and also return it (separate from session)
import { randomBytes } from "crypto";
app.get("/csrf-alt", (req, res) => {
  const t = randomBytes(24).toString("hex");
  res.cookie("csrf_t", t, { secure: true, sameSite: "lax" }); // readable by JS
  res.json({ t });
});
// client: read token from response and echo in header with credentials
const { t } = await (await fetch("/csrf-alt", { credentials: "include" })).json();
await fetch("/api/transfer", {
  method: "POST",
  credentials: "include",
  headers: { "X-CSRF-Token": t, "Content-Type": "application/json" },
  body: JSON.stringify({ amount: 100 })
});

🖼 Sample Assessment Report by our tool to check Website Vulnerability

Sample vulnerability assessment report generated with our free tool, providing insights into possible vulnerabilities.
Sample vulnerability assessment report generated with our free tool, providing insights into possible vulnerabilities.

Best practices checklist

  • Always send requests with credentials: "include" / axios.defaults.withCredentials = true.

  • Validate a fresh token per session; rotate on login.

  • Prefer SameSite=Lax or Strict on cookies; always Secure + HttpOnly for session.

  • Block risky methods without tokens (POST/PUT/PATCH/DELETE).

  • Log and alert on CSRF validation failures.


Need hands-on help?

Subscribe on LinkedIn https://www.linkedin.com/build-relation/newsletter-follow?entityUrn=7327563980778995713

Try our free Website Security Checker: https://free.pentesttesting.com/

Comments

Popular posts from this blog

Fix Sensitive Data Exposure in Symfony Apps

Fix Security Misconfiguration Issues in Symfony

Open Redirect Vulnerability in Symfony