lewisdale.dev/src/blog/posts/2022/10/simple-csrf-token-middleware-for-express.md
2023-12-26 14:35:09 +00:00

2.5 KiB
Raw Blame History

title date slug
Simple CSRF token middleware for express 2022-10-23T00:00:00 simple-csrf-token-middleware-for-express

Ive been doing some Express development in Typescript recently, and I realised that there are no well-maintained CSRF libraries for Express anymore. Plus, CSRF is actually quite simple to implement and it meant I could avoid adding yet-another dependency to my project.

Heres the basic implementation. It depends on there already being some form of user sesion (in my case, its express-session):

import { Request, Response, NextFunction } from 'express';

export const csrfMiddleware = (req: Request, res: Response, next: NextFunction) => {
	const previousCsrf = req.session.csrf;

	// I've set up a manual dependency injection container with some middleware
	// You can substitute this with whatever library you want to use to
	// generate random strings
	// In my case this is a wrapper for Node.Crypto.randomBytes
	req.session.csrf = req.container.get_random_string(128);

	// Make the token available to templates as a variable
	res.locals.csrf = req.session.csrf;

	if (req.method !== "GET" && (previousCsrf !== req.body["_csrf"] || !previousCsrf)) {
		// Unless the request is a GET request, which doesn't use CSRF
		// compare the CSRF from the request with the token stored in session
		// Send a 403 Forbidden response if they don't match
		// or there was no stored csrf
		return res.sendStatus(403);
	}

	// Move to the next function
	next();
}

Its a fairly basic implementation, but it hits most of the right notes. The tokens are regenerated on each request, so its relatively resilient to brute force attacks. It will reject requests if there is no csrf token in the session, which prevents attacks before a session has been created.

Its got flaws, naturally: the token is tied to a session ID, for one. The tokens are also not encrypted with any sort of secret so they are, in theory, exposed. But as a first pass its a decent start. Theres also an issue with using the back button, as this will no longer work with the regnerated csrf token.

The OWASP CSRF Prevention Cheat Sheet has more information and advice on creating secure CSRF tokens. Some improvements to this approach include: encrypt the token, store it as a cookie with a time range, use SameSite cookie attributes.