mini-csrf
v1.0.4
Published
A small, stateless, session-less CSRF protection middleware for Express
Maintainers
Readme
mini-csrf

A smol CSRF protection middleware for Express applications using a stateless, session-less, HMAC token.
Introduction
This implementation is probably the smallest possible stateless Cross-Site Request Forgery (CSRF) protection for your Node.js/Express applications that's usably secure. It does not comply with the OWASP.
The use case for it would be when you need light-weight stateless non-session based CSRF protection. If you are using sessions you should use small-csrf instead since it implements an OWASP compliant Double-Submit Cookie Pattern.
Method
The CSRF token is a HMAC hash of the browser user-agent, IP address, the time and a server secret. The token and the time are inserted into the web page that calls a mutating route. The server is able to confirm the correctness of the token by re-creating it, then the time provided is checked to ensure its within the tolerance.
Token comparisons are done in constant time to avoid timing attacks.
Comparisons
| Feature | mini-csrf | Double Submit Cookies | Synchronizer Token | | ------------------------------------- | ------------------------------- | --------------------- | ------------------------------ | | Session required | ❌ | ❌ | ✅ | | Stateless | ✅ | ✅ | ❌ | | Forgery resistance | ✅ (via HMAC) | ✅ (via cross-check) | ✅ (via match to stored value) | | Tied to browser | Partially (IP/UA) | ✅ (via cookie) | ✅ (via session cookie) | | Resistant to replay | ❌ (within time window) | ❌ | ❌ | | Prevents CSRF with stolen cookies | ❌ (attacker can guess context) | ✅ | ✅ |
Installation
npm install mini-csrfQuick Start
Basic integration with Express:
// npm install express mini-csrf
import express from "express";
import csrfProtection from "mini-csrf";
const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
// CSRF protection middleware
const csrf = csrfProtection({
secret: "at-least-32-characters-long-csrf-secret",
});
app.use(csrf.middleware);
// render a form with CSRF token
// the csrf Token Html actually contains the token and time
app.get("/form", (req, res) => {
res.send(`
<form action="/submit" method="POST">
${csrf.csrfTokenHtml(req)}
<input type="text" name="data">
<button type="submit">Submit</button>
</form>
`);
});
app.post("/submit", (req, res) => {
// if the request reaches here, CSRF validation passed since
// it's done in the middleware
res.send("Form submitted successfully!");
});
// handler for CSRF errors
app.use((err, req, res, next) => {
if (err.code === "EBADCSRFTOKEN") {
return res.status(403).send("Invalid CSRF token. Form submission failed.");
}
next(err);
});
app.listen(3000);Configuration
The csrfProtection function accepts a configuration object with the following options:
Required Parameters
secret (string)
The server secret used to generate HMAC tokens. Must be at least 32 characters long.
const csrf = csrfProtection({
secret: "at-least-32-characters-long-csrf-secret",
});Optional Parameters
fieldNames (object)
Customize the names of the hidden form fields. Default values shown below:
const csrf = csrfProtection({
secret: "at-least-32-characters-long-csrf-secret",
fieldNames: {
token: "_csrf_token", // default
time: "_csrf_time", // default
},
});ttl (number)
Time-to-live for tokens in milliseconds. Tokens older than this will be rejected. Default is 3600000 (1 hour).
const csrf = csrfProtection({
secret: "at-least-32-characters-long-csrf-secret",
ttl: 1800000, // 30 minutes
});Complete Configuration Example
const csrf = csrfProtection({
secret: "at-least-32-characters-long-csrf-secret",
fieldNames: {
token: "csrf_token",
time: "csrf_timestamp",
},
ttl: 900000, // 15 minutes
});Contributing
Contributions and issues are welcome, especially for security concerns.
Tests
Uses the built-in Node test runner - available from Node 20
npm test to run
Example App
To run a local demo of mini-csrf in an Express app from a cloned repo:
cd example && npm install && npm startThen visit http://localhost:3000
License
Versions
- 1.0.0 - initial
- 1.0.1 - README fixes
- 1.0.2 - ReaDmE fIXeS
- 1.0.3 - types, input validation
- 1.0.4 - NPM publish via GitHub Action
