npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pfeiferio/express-csrf

v1.3.1

Published

Short-lived, single-use CSRF tokens for Express — bound to browser context, built for multi-instance deployments. — a modern, dependency-free alternative to csurf

Readme

@pfeiferio/express-csrf

Short-lived, single-use CSRF tokens for Express — bound to browser context, built for multi-instance deployments. A modern, dependency-free alternative to the deprecated csurf package.

npm version TypeScript License: MIT Node.js codecov

This package provides Express middleware for CSRF protection using short-lived, one-time tokens that are cryptographically bound to the requesting browser. Tokens are flow-bound rather than session-bound, support parallel validity, and are designed to work correctly in multi-instance deployments via an optional external store adapter.


Features

  • ✅ Time-limited CSRF tokens with configurable TTL
  • ✅ Single-use tokens with replay protection
  • ✅ Browser-bound signatures (IP prefix + User-Agent)
  • ✅ Optional Origin header validation
  • ✅ Optional JSON-only enforcement
  • ✅ In-memory token registry with automatic cleanup
  • ✅ External store adapter interface for Redis and multi-instance setups
  • ✅ Graceful shutdown via AbortSignal
  • ✅ Granular validation failure reasons for custom error handling
  • ✅ Full TypeScript support
  • ✅ No dependencies

Security Model

  • Tokens are HMAC-signed using a server-side secret.
  • Each token embeds:
    • random nonce
    • expiration timestamp
    • browser signature (IP prefix + User-Agent)
  • Tokens are bound to HTTP method and route scope.
  • Tokens are single-use and replay-protected.

Installation

npm install @pfeiferio/express-csrf

Basic Usage

The middleware reads the CSRF secret from req.cookies by default. If you use cookie-parser, register it before csrfMiddleware:

import express from 'express'
import cookieParser from 'cookie-parser'
import {csrfMiddleware} from '@pfeiferio/express-csrf'

const app = express()

app.use(cookieParser())
app.use(express.json())
app.use(csrfMiddleware({
  csrfSecretCookie: {
    name: '__csrf'
  }
}))

// CSRF token is available on GET requests
app.get('/form', (req, res) => {
  res.json({csrfToken: res.locals.csrfToken})
  // or: req.csrf.generateToken()
})

app.post('/submit', (req, res) => {
  res.json({ok: true})
})

The middleware automatically:

  • Sets an HttpOnly CSRF secret cookie on the first request
  • Generates a token on GET requests via res.locals.csrfToken
  • Validates the token on state-changing requests (POST, PUT, PATCH, DELETE)

Token Transmission

Send the token in one of the following request headers:

X-CSRF-Token: <token>
X-XSRF-Token: <token>

Or in the request body as _csrf.

Note: Reading _csrf from the request body requires a body parser (e.g. express.json()) to be registered before the CSRF middleware.


Custom Cookie Reader

By default, the middleware reads cookies from req.cookies. If you use a different cookie solution or want to read from signed cookies, provide a custom cookieReader:

// with signed cookies
app.use(csrfMiddleware({
  csrfSecretCookie: {
    name: '__csrf',
    cookieReader: (req) => req.signedCookies
  }
}))

// without cookie-parser — parse manually
app.use(csrfMiddleware({
  csrfSecretCookie: {
    name: '__csrf',
    cookieReader: (req) => {
      // your own cookie parsing logic
      return parseCookies(req.headers.cookie ?? '')
    }
  }
}))

Multi-Instance Deployments

By default, the used-token registry is in-memory and process-local. In a multi-instance setup (PM2 cluster, Kubernetes), a token validated on instance A could be replayed on instance B.

To prevent this, provide an external store adapter:

import {csrfMiddleware} from '@pfeiferio/express-csrf'
import type {CsrfStoreAdapter} from '@pfeiferio/express-csrf'
import {createClient} from 'redis'

const redis = createClient()
await redis.connect()

const redisStore: CsrfStoreAdapter = {
  has: async (key) => (await redis.exists(key)) === 1,
  set: async (key, ttlMs) => {
    await redis.set(key, '1', {PX: ttlMs})
  }
}

app.use(csrfMiddleware({
  csrfSecretCookie: {name: '__csrf'},
  internals: {store: redisStore}
}))

The store is responsible for its own TTL management — the middleware passes ttlMs to set(). An in-memory local cache runs in parallel to reduce store lookups for already-consumed tokens.


Custom Error Handling

app.use(csrfMiddleware({
  csrfSecretCookie: {name: '__csrf'},
  guard: {
    onTokenRejected: (req, res, next, result) => {
      console.warn('CSRF rejected:', result.reason)
      res.status(403).json({error: result.reason})
    }
  }
}))

Possible reason values: missing_secret, origin_mismatch, invalid_content_type, missing_token, invalid_structure, expired, browser_signature_mismatch, invalid_signature, token_already_used, unknown.


Excluding Routes

app.use(csrfMiddleware({
  csrfSecretCookie: {name: '__csrf'},
  guard: {
    // Skip all CSRF logic for static assets and Vite dev server
    exclude: (req) => req.path.includes('.') || req.path.startsWith('/@'),

    // Skip validation only for webhooks
    skipValidation: (req) => req.path.startsWith('/webhooks/'),
  }
}))

exclude skips both token creation and validation. skipValidation and skipTokenCreation allow finer control.


Graceful Shutdown

The internal cleanup timer is automatically stopped when the signal is aborted.

const controller = new AbortController()

app.use(csrfMiddleware({
  csrfSecretCookie: {name: '__csrf'},
  internals: {signal: controller.signal}
}))

process.on('SIGTERM', () => controller.abort())

req.csrf

The middleware attaches a csrf object to every request:

req.csrf.hasSecret()              // boolean — whether a CSRF secret cookie exists
req.csrf.generateToken()          // string | false — generate a new token on demand
req.csrf.isExcluded()             // boolean
req.csrf.isTokenCreationSkipped() // boolean
req.csrf.isValidationSkipped()    // boolean
req.csrf.isValidToken(token)      // Promise<boolean> — manually validate a given CSRF token string

Configuration Reference

csrfMiddleware({
  csrfToken: {
    ttl: 5 * 60 * 1000,               // Token lifetime in ms (default: 5 minutes)
    lookup: (req) => ...,              // Custom token extractor
    scopeResolver: (req) => ...,       // Custom scope binding (default: METHOD:path)
  },
  csrfSecretCookie: {
    name: '__csrf',                    // Cookie name (default: '__csrf')
    path: '/',
    ttl: 7 * 24 * 60 * 60 * 1000,    // Cookie lifetime in ms (default: 7 days)
    domain: undefined,
    secure: true,
    sameSite: 'strict',               // 'strict' | 'lax' | 'none'
    cookieReader: (req) => req.cookies ?? {},  // Custom cookie reader (default: req.cookies ?? {})
  },
  guard: {
    jsonOnly: true,                   // Require application/json Content-Type (checked via raw header when no body parser is present)
    origin: false,                    // Expected Origin header value, or false to disable
    onTokenRejected: ...,             // Custom rejection handler
    exclude: (req) => boolean,
    skipTokenCreation: (req) => boolean,
    skipValidation: (req) => boolean,
  },
  internals: {
    store: CsrfStoreAdapter,          // External store for multi-instance deployments
    cleanupProcess: (ctx, opts) => {
    },// Custom cleanup handler
    signal: AbortSignal,              // For graceful shutdown
    debug: (msg, ctx) => {
    },          // Debug logger
  }
})

Design Goals

  • Flow-bound tokens — tokens are scoped to a specific HTTP method and route, preventing reuse across endpoints
  • Parallel validity — multiple tokens can be valid simultaneously, supporting SPAs with concurrent requests
  • No session dependency — works without a session store
  • Multi-instance ready — external store adapter keeps replay protection consistent across instances
  • Explicit over implicit — no magic, no monkey-patching, predictable behavior

License

MIT