# CSRF Protection Checklist for REST APIs and Web Forms

> A practical CSRF protection checklist covering token patterns, SameSite cookies, fetch metadata, and common implementation mistakes for APIs and forms.

- **Published:** 2025-06-06
- **Author:** Clixo
- **Reading time:** 6 min read
- **Tags:** csrf, api-security, web-security, cookies, authentication
- **Canonical URL:** https://clixo.sh/blog/csrf-protection-checklist-rest-api-web-forms

Cross-site request forgery (CSRF) is one of the most misunderstood vulnerabilities in web security. Developers frequently implement partial defenses — checking the referrer header, switching to POST-only requests, or adding CORS headers — and conclude they are protected. These measures reduce noise but do not constitute real CSRF protection. A determined attacker bypasses all of them.

This checklist covers what actually works, where each defense applies, and the implementation details that determine whether your protection is genuine or cosmetic.

## CSRF Protection Checklist for REST APIs and Web Forms

### 1. Understand when your application is actually vulnerable

CSRF is only exploitable when your application uses cookie-based authentication and the browser automatically sends those cookies on cross-origin requests. If your API authenticates via a custom `Authorization` header with a Bearer token stored in JavaScript memory (not a cookie), CSRF does not apply — the browser will not automatically attach that header to cross-origin requests.

If you use cookies for session management or authentication, work through the rest of this checklist.

```mermaid
flowchart TD
  A[Incoming request] --> B{"Cookie-based auth?"}
  B -- No --> C[CSRF not applicable]
  B -- Yes --> D{"SameSite Lax or Strict?"}
  D -- Yes --> E{"State-mutating request?"}
  D -- No --> F[High CSRF risk]
  E -- Yes --> G{CSRF token valid?}
  E -- No --> H["Safe - read-only"]
  G -- Valid --> I[Process request]
  G -- Invalid --> J[Reject request]
```

### 2. Set SameSite on all authentication cookies

The `SameSite` cookie attribute is now the most practical first line of defense:

- **`SameSite=Strict`**: The cookie is never sent on any cross-site request. This is the most restrictive and breaks use cases like navigating to your site from an email link while expecting the user to be authenticated.
- **`SameSite=Lax`**: The cookie is sent on top-level GET navigations but not on cross-site POST, PUT, DELETE, or fetch requests. This is the browser default for cookies without an explicit `SameSite` attribute in modern browsers.
- **`SameSite=None; Secure`**: The cookie is sent on all cross-site requests. Only use this if your architecture requires cross-site cookie sharing, and always pair it with CSRF tokens.

For most applications, `SameSite=Lax` with `Secure` and `HttpOnly` flags is the right default. For highly sensitive operations, `Strict` is preferable.

### 3. Add synchronizer tokens for state-mutating operations

The synchronizer token pattern remains the most reliable CSRF defense and is required when `SameSite=None` is necessary or when you need defense-in-depth beyond cookie attributes.

Implementation requirements:

- Generate a cryptographically random token per session (or per form)
- Store it server-side, associated with the session
- Embed it in forms as a hidden field, or send it to the client for inclusion in a custom request header
- Validate the token on every state-mutating request before processing it
- Treat a missing or mismatched token as a rejected request, not a warning

Do not store the CSRF token in a location accessible to JavaScript if you have XSS vulnerabilities — XSS defeats CSRF tokens.

### 4. Use the Double Submit Cookie pattern for stateless APIs

If your API is stateless and you do not want to store tokens server-side, the Double Submit Cookie pattern is an alternative:

- Set a random value as a cookie and require the client to also send it in a custom request header or body parameter
- The server validates that both values match

An attacker cannot read the cookie value from a cross-origin context (assuming the cookie does not have `SameSite=None`), so the values cannot match in a forged request. This breaks down if the application has subdomain injection vulnerabilities, so it requires that all subdomains are trustworthy and that cookies are scoped appropriately with the `domain` attribute.

### 5. Validate the Origin and Referer headers as a secondary check

Check the `Origin` header on all state-mutating requests. If it does not match your expected origin(s), reject the request. Fall back to the `Referer` header when `Origin` is absent.

Important caveats:

- Do not rely on this as your only CSRF defense — headers can be absent in some privacy configurations
- Do not allowlist `null` as a valid origin — `null` appears on requests from sandboxed iframes and file:// contexts and is commonly used in CSRF exploits
- Maintain an explicit allowlist of permitted origins; do not infer from the request

### 6. Implement Fetch Metadata request headers validation

Modern browsers send `Sec-Fetch-Site`, `Sec-Fetch-Mode`, and `Sec-Fetch-Dest` headers that identify the context of a request. A request with `Sec-Fetch-Site: cross-site` combined with `Sec-Fetch-Mode: navigate` and a non-safe HTTP method is an unusual combination that warrants rejection.

Fetch Metadata policies are a defense-in-depth measure. They are not supported in all browser versions and should supplement, not replace, token-based defenses.

### 7. Do not use GET for state-mutating operations

GET requests should be idempotent and free of side effects. If a GET request can change state — delete a record, send an email, transfer funds — an attacker can trigger it with a simple image tag embedding the URL. This is not primarily a CSRF defense; it is a fundamental HTTP design principle with direct security implications.

### 8. Verify your framework's built-in CSRF protection is enabled

Most mature web frameworks (Django, Rails, Laravel, Spring Security, ASP.NET Core) include CSRF protection that is either on by default or trivial to enable. Check that:

- It has not been disabled for convenience
- API routes use appropriate exemptions only for truly stateless, token-authenticated endpoints
- You are not accidentally exempting too broad a set of routes

### 9. Review cookie scope settings

A cookie scoped too broadly creates vectors that token-based defenses cannot close:

- Set the `domain` attribute to your specific domain, not a parent domain shared with other applications
- Set the `path` attribute to the minimum necessary path
- Use `Secure` to prevent transmission over HTTP
- Use `HttpOnly` to prevent JavaScript access when XSS protection is the goal

### 10. Test your CSRF protection before going to production

Manual verification: attempt a cross-origin form POST from a locally hosted HTML file to your application's state-mutating endpoints. If the request succeeds without a valid CSRF token, the protection is incomplete.

Automated testing: include CSRF in your DAST scan configuration. Tools like OWASP ZAP can probe for missing anti-CSRF tokens in form submissions.

## Common Mistakes That Invalidate CSRF Protection

- Accepting CSRF tokens in query parameters rather than body or headers (query parameters appear in server logs and referrer headers)
- Generating tokens that are not truly random (sequential or predictable tokens are guessable)
- Failing to invalidate CSRF tokens on logout
- Allowing cross-origin requests with `Access-Control-Allow-Origin: *` when cookies are in use

For teams building APIs and web applications that need security architecture reviewed or built from scratch, [Start a build](https://clixo.sh/#contact).

---

Clixo · 1141 W Bryn Mawr Ave, Itasca, IL 60143, US · [hello@clixo.sh](mailto:hello@clixo.sh)
[Start a build](https://clixo.sh/#contact) · [All services](https://clixo.sh/services) · [Agent guide (llms.txt)](https://clixo.sh/llms.txt)
