csrf-middleware
The csrf middleware protects your application against Cross-Site Request Forgery (CSRF) attacks. It uses the double-submit cookie pattern to verify that form submissions and state-changing requests originate from your site.
Installation
The CSRF middleware is included with Remix. No additional installation is needed.
Import
import { csrf, getCsrfToken } from 'remix/csrf-middleware'API
csrf(options?)
Returns a middleware function that generates and validates CSRF tokens.
let router = createRouter({
middleware: [
session(sessionCookie, sessionStorage),
csrf(),
],
})Session middleware required
The csrf middleware stores the CSRF token in the session. The session middleware must appear before csrf in the middleware chain.
getCsrfToken(context)
Helper function to retrieve the current CSRF token from the request context. Use this to include the token in forms and API requests.
import { getCsrfToken } from 'remix/csrf-middleware'
router.map(route, ({ context }) => {
let token = getCsrfToken(context)
return html`
<form method="post">
<input type="hidden" name="_csrf" value="${token}" />
<button type="submit">Submit</button>
</form>
`
})Options
| Option | Type | Default | Description |
|---|---|---|---|
fieldName | string | '_csrf' | The form field name to check for the CSRF token. |
headerName | string | 'X-CSRF-Token' | The HTTP header name to check for the CSRF token (for API requests). |
ignoreMethods | string[] | ['GET', 'HEAD', 'OPTIONS'] | HTTP methods that are exempt from CSRF validation. These are safe methods that should not cause side effects. |
How It Works
The middleware uses the double-submit cookie pattern:
Token generation --- On safe method requests (GET, HEAD, OPTIONS), the middleware generates a cryptographically random token and stores it in the session.
Token validation --- On unsafe method requests (POST, PUT, PATCH, DELETE), the middleware reads the token from either:
- The form body field (default:
_csrf) - The request header (default:
X-CSRF-Token)
It then compares this token against the one stored in the session. If they do not match, the request is rejected with a
403 Forbiddenresponse.- The form body field (default:
Safe methods are exempt --- GET, HEAD, and OPTIONS requests are never validated because they should not produce side effects.
Examples
Basic Form Protection
import { createRouter } from 'remix/fetch-router'
import { session } from 'remix/session-middleware'
import { csrf, getCsrfToken } from 'remix/csrf-middleware'
import { formData } from 'remix/form-data-middleware'
let router = createRouter({
middleware: [
session(sessionCookie, sessionStorage),
csrf(),
formData(),
],
})
// Render a form with the CSRF token
router.map(routes.newPost, ({ context }) => {
let token = getCsrfToken(context)
return html`
<form method="post" action="/posts">
<input type="hidden" name="_csrf" value="${token}" />
<input type="text" name="title" />
<textarea name="body"></textarea>
<button type="submit">Create Post</button>
</form>
`
})API Requests with CSRF Header
For JavaScript-initiated requests, send the token in a header instead of a form field:
// Server: expose the token to the client
router.map(routes.app, ({ context }) => {
let token = getCsrfToken(context)
return html`
<html>
<head>
<meta name="csrf-token" content="${token}" />
</head>
<body><!-- ... --></body>
</html>
`
})
// Client: read the token and include it in fetch requests
let csrfToken = document.querySelector('meta[name="csrf-token"]').content
fetch('/api/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken,
},
body: JSON.stringify({ title: 'Hello' }),
})Custom Field and Header Names
csrf({
fieldName: 'authenticity_token',
headerName: 'X-Authenticity-Token',
})Related
- Middleware --- How middleware works in Remix.
- Security Guide --- CSRF in the context of application security.
- cop-middleware --- An alternative tokenless cross-origin protection approach.
- Forms & Mutations --- Working with forms in Remix.