Skip to content

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 ​

ts
import { csrf, getCsrfToken } from 'remix/csrf-middleware'

API ​

csrf(options?) ​

Returns a middleware function that generates and validates CSRF tokens.

ts
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.

ts
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 ​

OptionTypeDefaultDescription
fieldNamestring'_csrf'The form field name to check for the CSRF token.
headerNamestring'X-CSRF-Token'The HTTP header name to check for the CSRF token (for API requests).
ignoreMethodsstring[]['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:

  1. Token generation --- On safe method requests (GET, HEAD, OPTIONS), the middleware generates a cryptographically random token and stores it in the session.

  2. 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 Forbidden response.

  3. Safe methods are exempt --- GET, HEAD, and OPTIONS requests are never validated because they should not produce side effects.

Examples ​

Basic Form Protection ​

ts
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:

ts
// 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 ​

ts
csrf({
  fieldName: 'authenticity_token',
  headerName: 'X-Authenticity-Token',
})

Released under the MIT License.