salad-csrf
v1.0.0
Published
CSRF protection middleware for Express with preauth/auth scopes.
Maintainers
Readme
salad-csrf
Lightweight CSRF protection middleware for Express with pre-auth and auth scopes.
Install
npm install salad-csrfSet a secret in your environment:
CSRF_SECRET="your-strong-secret"Usage (ESM)
import express from "express";
import cookieParser from "cookie-parser";
import {
init,
csrfTokenHandler,
preAuthScopeResolver,
authScopeResolver,
requireCsrf,
} from "salad-csrf";
const app = express();
app.use(cookieParser());
init({
ttl: 60 * 15,
cookieName: "csrf",
sessionContext: {
getSessionId: (req) => req.user?.id,
},
isProd: process.env.NODE_ENV === "production",
});
app.get("/csrf", csrfTokenHandler(preAuthScopeResolver));
app.post("/preauth-action", requireCsrf("preauth"), (req, res) => {
res.json({ ok: true });
});
app.get("/auth-csrf", csrfTokenHandler(authScopeResolver()));
app.post("/auth-action", requireCsrf("auth"), (req, res) => {
res.json({ ok: true });
});Usage (CommonJS)
const express = require("express");
const cookieParser = require("cookie-parser");
const {
init,
csrfTokenHandler,
preAuthScopeResolver,
authScopeResolver,
requireCsrf,
} = require("salad-csrf");
const app = express();
app.use(cookieParser());
init({
ttl: 60 * 15,
cookieName: "csrf",
sessionContext: {
getSessionId: (req) => req.user?.id,
},
isProd: process.env.NODE_ENV === "production",
});
app.get("/csrf", csrfTokenHandler(preAuthScopeResolver));
app.post("/preauth-action", requireCsrf("preauth"), (req, res) => {
res.json({ ok: true });
});
app.get("/auth-csrf", csrfTokenHandler(authScopeResolver()));
app.post("/auth-action", requireCsrf("auth"), (req, res) => {
res.json({ ok: true });
});How it works
- A CSRF binding cookie is set (HTTP-only, SameSite=Lax).
- A CSRF token is issued with:
hash: SHA-256 of the binding cookiescope:preauthorauthexp: expiry (seconds since epoch)sid: hashed session ID forauthscope
- On protected requests, the token in
x-csrf-tokenis validated against the cookie and (forauth) the session ID.
API
init({ ttl, cookieName, sessionContext, isProd })
Required:
ttl(number, seconds)
Optional:
cookieName(string, defaults tocsrf_default)sessionContext(object withgetSessionId(req)forauthscope)isProd(boolean)
Behavior:
- When
isProdistrue, any validation or runtime errors returned to the client are masked asPERMISSION DENIED. - When
isProdisfalse, exact error messages are returned to help debugging.
csrfTokenHandler(scopeResolver)
Returns an Express handler that:
- Ensures the CSRF binding cookie exists.
- Issues a token based on the provided scope resolver.
- Responds with
{ csrfToken }.
preAuthScopeResolver
Resolver for pre-auth scope:
csrfTokenHandler(preAuthScopeResolver)authScopeResolver()
Resolver for auth scope:
csrfTokenHandler(authScopeResolver())Requires sessionContext.getSessionId(req) to return a session identifier.
requireCsrf(scope)
Express middleware that validates CSRF tokens on unsafe methods (POST, PUT, PATCH, DELETE). Safe methods (GET, HEAD, OPTIONS) bypass CSRF validation.
Example:
app.post("/protected", requireCsrf("preauth"), handler);Client usage
- Fetch a CSRF token:
curl -c cookie.txt http://localhost:3000/csrf- Send it back on unsafe requests:
curl -b cookie.txt -H "x-csrf-token: <token>" -X POST http://localhost:3000/preauth-actionTests
npm testBuild
npm run buildNotes
- You must set
CSRF_SECRETin the environment before issuing or validating tokens. - Ensure
cookie-parseris registered before these handlers soreq.cookiesis available. - Consider HTTPS in production so
securecookies are sent.
