如何为API-Centric架构的Web应用实现可靠CSRF防护?
Great question! When building an API-centric web app where your frontend relies on AJAX to communicate with a REST API, CSRF protection needs some tailored approaches—since you’re not dealing with full page reloads or traditional form submissions. Let’s break down reliable, battle-tested strategies you can implement:
1. Cookie-to-Header Token Synchronization (Most Common & Reliable)
This is the gold standard for AJAX-based apps. The idea is to sync a CSRF token between a browser cookie and a custom request header, then validate that they match on the server.
How to implement:
Server-side setup:
When a user first visits your app (or logs in), generate a cryptographically random CSRF token. Set it as a non-HttpOnly cookie (so your frontend can read it) with strict security attributes:SameSite=Strict(orLaxif you need to support cross-site navigation like OAuth callbacks)Secure(only send over HTTPS—always use HTTPS in production!)Path=/(so it’s available across your app)
Example with Node.js/Express:
const csurf = require('csurf'); const cookieParser = require('cookie-parser'); app.use(cookieParser()); // Configure CSRF middleware app.use(csurf({ cookie: { httpOnly: false, sameSite: 'Strict', secure: process.env.NODE_ENV === 'production', path: '/' } })); // Expose the token to your frontend (either inject into the page or via an API endpoint) app.get('/api/csrf-token', (req, res) => { res.json({ csrfToken: req.csrfToken() }); });Frontend implementation:
Before making an AJAX request, fetch the CSRF token (either from the injected page variable or the dedicated endpoint) and add it to a custom header likeX-CSRF-Token. Ensure your request includes credentials (cookies) so the server can access the CSRF cookie.Example with Fetch API:
async function fetchCsrfToken() { const res = await fetch('/api/csrf-token'); const data = await res.json(); return data.csrfToken; } async function makeAuthenticatedRequest(url, options = {}) { const csrfToken = await fetchCsrfToken(); return fetch(url, { ...options, headers: { ...options.headers, 'X-CSRF-Token': csrfToken, 'Content-Type': 'application/json' }, credentials: 'include' // Critical: sends cookies with the request }); } // Usage makeAuthenticatedRequest('/api/user/profile', { method: 'PUT', body: JSON.stringify({ name: 'New Name' }) });Server-side validation:
The server will automatically compare the token in theX-CSRF-Tokenheader with the value in the CSRF cookie. If they don’t match, reject the request with a 403 Forbidden response. Most frameworks (like Express withcsurf, Django, Spring Boot) have built-in middleware to handle this.
2. Double Submit Cookie (For Stateless APIs)
If your API is stateless (e.g., using JWT for authentication), this strategy works well because you don’t need to store the CSRF token on the server. The server only checks that the token sent in the request matches the one in the cookie.
How to implement:
- Server: Generate a random CSRF token and set it as a non-HttpOnly cookie (same security attributes as above). No need to store the token server-side.
- Frontend: Read the cookie value and include it either in a request header or the request body (e.g., add a
csrfTokenfield to your JSON payload). - Server validation: Extract the token from the request (header/body) and compare it to the cookie value. If they match, proceed; otherwise, reject.
Example with Spring Boot:
// Set the CSRF cookie @GetMapping("/") public void setCsrfCookie(HttpServletResponse response) { String csrfToken = UUID.randomUUID().toString(); Cookie csrfCookie = new Cookie("CSRF-TOKEN", csrfToken); csrfCookie.setHttpOnly(false); csrfCookie.setSameSite("Strict"); csrfCookie.setSecure(true); csrfCookie.setPath("/"); response.addCookie(csrfCookie); } // Validate the token @PostMapping("/api/transactions") public ResponseEntity<?> createTransaction( @RequestBody TransactionRequest request, @CookieValue("CSRF-TOKEN") String cookieToken ) { if (!cookieToken.equals(request.getCsrfToken())) { return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Invalid CSRF token"); } // Process transaction return ResponseEntity.ok("Transaction created"); }
3. SameSite Cookie Attributes (Layered Defense)
While not a standalone solution, setting SameSite=Strict or SameSite=Lax on your authentication and CSRF cookies adds a critical layer of protection. Browsers will block these cookies from being sent in cross-site requests, which eliminates most CSRF vectors by default.
SameSite=Strict: Best for sensitive actions (like payments, profile updates) where you don’t want cookies sent even if the user clicks a link from another site.SameSite=Lax: More flexible—allows cookies to be sent in cross-site GET requests (like navigating to your site from a link) but blocks them in POST/PUT/DELETE requests from other sites.
Note: If you need to support cross-site workflows (e.g., OAuth login callbacks), you may need to temporarily relax SameSite or use a separate cookie for those flows.
4. Origin/Referer Header Validation (Secondary Check)
As an extra layer, validate the Origin or Referer header to ensure the request comes from a trusted domain. This works because browsers include these headers in cross-origin requests, letting you verify the request’s source.
How to implement:
- On the server, maintain a list of trusted frontend domains (e.g.,
https://yourapp.com,https://staging.yourapp.com). - Check if the
Originheader (preferred, as it’s more reliable) orRefererheader matches one of your trusted domains. If not, reject the request.
Example with Express:
const TRUSTED_ORIGINS = ['https://yourapp.com', 'https://staging.yourapp.com']; app.use((req, res, next) => { const origin = req.headers.origin; // Allow requests without an Origin (e.g., same-origin requests) or from trusted origins if (!origin || TRUSTED_ORIGINS.includes(origin)) { next(); } else { res.status(403).send('Unauthorized origin'); } });
Important caveats:
- Some older browsers don’t send the
Originheader. - The
Refererheader can be stripped by privacy tools or proxies. - Never rely on this as your only CSRF protection—use it alongside token-based strategies.
- Always use HTTPS: Unencrypted HTTP exposes cookies and tokens to interception, rendering CSRF protection useless.
- Avoid storing tokens in
localStorage: WhilelocalStorageisn’t vulnerable to CSRF, it’s high-risk for XSS attacks. Stick to cookies with properHttpOnly(for auth tokens) andSameSiteattributes. - Generate strong tokens: Use cryptographically secure random generators (e.g.,
crypto.randomBytes()in Node.js,UUID.randomUUID()in Java) to create CSRF tokens—never use predictable values. - Handle CORS correctly: If your frontend is on a different domain than the API, set
Access-Control-Allow-Credentials: trueand restrictAccess-Control-Allow-Originto your trusted domains (never use*).
内容的提问来源于stack exchange,提问作者Obvious_Grapefruit

