CSRF token issues with Express - node.js

I am trying to get CSRF protection working using csurf and express. My app uses Angular for the front end, so I figured adding this to my app would be enough:
app.use(cookieParser('test secret'));
app.use(cookieSession({
secret: 'test secret'
}));
app.use(csrf({
cookie: {
key: 'XSRF-TOKEN'
}
}));
However, when I try to make POST requests, I get 403 ("invalid CSRF token") errors.
As far as I can tell, the issue resides in the csrf-tokens module: at line 44, the expected variable looks like qMnHLQGhivxECx5WtwuktDNA-snimacq30z-XNh2X-KTpdlkU6Og while the secret and token variables look like qMnHLQGhivxECx5WtwuktDNA. Since expected and token are different, a 403 error occurs.
I have, so far, tried the following:
Setting secure: false and/or signed: true in my csurf options
Making sure that my cookie-session and cookie-parser middleware run before csurf
Re-installing all my npm modules just in case
Yelling at my computer
I could, potentially, switch to session-based tokens, but that wouldn't work as smoothly with Angular, and I'm not sure if it would even solve my problem.
Does anyone know how to fix this issue, or what the cause might be?
P.S. By the way, I've also noticed a (probably unrelated) issue: the req.csrfToken() method seems to return strings whose values are entirely unrelated to the expected value in csrf-tokens or to the string being stored in the cookie.

There is an error/bad implementation regarding csurf lib. The cookie "XSRF-TOKEN" does not store the csrf Token, but the secret that csurf uses to generate the csrf token. Therefore, the Angular part of your app identifies the XSRF-TOKEN and appends the header correctly to HTTP requests, but the csurf middleware does not recognize the secret as a valid CSRF token itself. To fix this problem, you should implement this as:
var cookieParser = require('cookie-parser');
var cookieSession = require('cookie-session');
app.use(cookieParser("secret"));
app.use(cookieSession({secret: "secret2"}));
app.use(csrf());
app.use(function (req, res, next) {
res.cookie("XSRF-TOKEN",req.csrfToken());
return next();
});
The req.csrfToken() will generate the csrf token based on the secret stored, in this case, in the req.session.csrfSecret variable, generated by the csrf middleware. Then, it will work.
EDIT: See this issue on GitHub

Related

Node express static with csrf

I'm trying to use csurf in nodejs, express and React Project. My csurf is working fine right now, but I want to double check if I did it right.
Here is my nodejs router and middleware structure:
app.use(cookieParser());
app.use("/api/...") // routes that don't need csrf
app.use("/form/...",csrf({cookie:true})) // form path with csrf middleware
app.use(express.static("/img")) // image folder which doesn't need csrf
app.use(csrf({ cookie: true })); // enable csrf for the rest of the app
app.all("*", function(req, res, next) {
res.header("X-CSRF-Token", req.csrfToken()); // set csrf to header
return next();
});
app.use(express.static("/SPA")); // frontend project
Current behavior:
When I first enter my web project, I have X-CSRF-Token: xxxxx and set-cookie: _csrf=yyyyy; Path=/ in my response headers
When I refresh my page, Cookie: _csrf=yyyyy; appears in the request headers section.
When I refresh my page, X-CSRF-Token changes to a different value.
Only X-CSRF-Token value passed through post request, _csrf value inside cookie throw 403.
Question:
A. I believe setting app.use(csrf({ cookie: true })) is redundant, but when I set it to false or removed app.all(...) part, the app throw 403 / Internal Server Error. How to fix it?
B. X-CSRF-Token changes everytime I refreshed my page, it's obviously a normal behavior since I put it in the header, but does it defeat the purpose of csrf? Since my project is SPA, do I really care that much?
Please point out if there were anything wrong with the logic / behavior ?
Thanks.
Since my project is SPA, do I really care that much?
If a client app (SPA or not) sends some data to the backend and the data changes backend's state either directly or indirectly, for example via an action performed by backend on client's behalf, then CSRF vulnerability exists and you should care unless SPA framework like Angular takes care of CSRF protection. Using SPA doesn't change anything with respect to CSRF, it doesn't help and it alleviates nothing.
With cookie-parser middleware the csrf middleware works like that:
Checks for its cookie with a predefined name in the incoming request. If not found then generates a secret key and puts its value (decorated a bit) into the response cookie hoping to find it in the next request. So the secret is not a secret anymore.
If cookie not found and the incoming request is mutating like POST (e.g. not GET, HEAD ...) then fail it e.g. send 403 back with cookie set. If non-mutating like GET then processing is finished.
If cookie found then check for the second piece of data, by default in few places including HTTP header with a predefined name. If not found or found and incorrect then fail incoming request. Otherwise processing is finished.
To ensure this check is successful you are responsible for 2 steps:
- on the backend call req.csrfToken() to obtain this second piece of data and store in the response. You have chosen to store it in HTTP header, it's fine. But you could have used any header name. Or you could have used <meta> tag in the <head> section.
- on the client take the second piece of data from the above header or <meta> tag in the backend response and put it into the request you are about to send assuming the request is mutating e.g. POST, PUT, etc. Furthermore, you need to put it into one of the predefined places in the request where csrf middleware searches by default.
Regarding your code:
1. The client code responsible for the second step is missing.
2. On the backend call csrf({options}) function once and store the returned value. You have called it twice. The return value, let's call it retValue, is the configured csrf middleware, use it as needed:
app.post(/<path>, retValue, ...req, res, next) => {...
3. As for the options, set httpOnly: true. Additionally, in production set secure: true:
csrf({cookie: {
httpOnly: true,
secure: true
}})
A. I believe setting app.use(csrf({ cookie: true })) is redundant,
but when I set it to false or removed app.all(...) part, the app
throw 403 / Internal Server Error. How to fix it?
By setting app.use(csrf{cookie:true}) before your routes you tell your app to pass each request that you get through csrf middleware (except those higher than this command). This middleware is supposed to do 3 things.
First is to set csrf cookie, if it's not present yet. This cookie is a secret which is needed to create/verify csrf tokens.
Second is to create new token when you call req.csrfToken().
Third is to verify tokens for all non-GET/HEAD/OPTION requests.
Also it's important to understand what you do with this command:
app.all("*", function(req, res, next) { res.header("X-CSRF-Token",
req.csrfToken()); // set csrf to header return next(); });
All your requests (get, post, put etc etc) they generate new token on the basis of the secret you have in your cookie. Same secret generates random tokens, as random salts are used for this purpose.
So in case you remove app.all part with req.csrfToken() you will not be generating any tokens, so verification will fail. And in case you remove app.use(csrf) part you will not be able to verify anything as this part does verification and you will fail to issue tokens as well. Because in other words the middleware will be turned off. So you can't remove any of these two commands as they serve different purpose.
B. X-CSRF-Token changes everytime I refreshed my page, it's obviously a normal behavior since I put it in the header, but does it defeat the purpose of csrf? Since my project is SPA, do I really care that much?
The purpose of csrf tokens is to verify whether request came from your website, because otherwise the client will have cookie only, but no token in the header. Pressing link initiating some actions on your website, but being on a different website will not generate csrf token for the client.

Express req.session is set and then empty when trying to POST

I have a MERN stack blog app that I'm making as a learning exercise. I'm relatively comfortable on the front end but have limited experience in node. I'm adding authentification after following a couple tutorials to get this far. I'm using passport.js as a framework to make a request to Google, get back an id and save as session info. However, after logging in, req.session is empty when making a post request, even though I can see a cookie in the dev tools.
I'm using bcrypt's hash to obscure the actual id, but that may not be best practice.
in blogAuth.js: req.session is defined.
bcrypt.hash(req.user._id.toString(), saltRounds, function(err, hash) {
req.session.user = hash;
//session is set here.
console.log(req.session); res.redirect(http://localhost:8080/articles/loggedIn);
});
but in api/articles.js: req.session is empty
router.post("/", (req, res, next) => {
const { body } = req;
// session is empty here. Why?
console.log(Session user, ${req.session.user});
console.log(req.session);
I have tried:
Including proxy in the client package.json:
express req.session empty with cookie-session
using withCredentials set to true when sending the POST from the
client:Node + Express + Passport: req.user Undefined
Rearranging the order of middleware: Routes must always come
afterward, but I feel like I'm doing this blindly and it usually results in an error
Here's some of the relevant files:
Server side:
- app.js
- blogAuth.js
- passport.js
- api/articles.js
Client side:
- the POST req
Entire project
I believe this is an issue with ordering. What is a good way to ensure that I order my middleware correctly? Or if the order looks correct, where else could this issue becoming from?
You are trying to use cookie-session as well as express-session. Since both of them will try to control the fate of req.session object, you will end up with empty session always.
Just remove one of them, and your session will be persisted.

Should I be able to re-use csrf tokens within the same session when using npm csurf?

When using csurf I've noticed that if I present a previously generated and used csrf token, it is still accepted as a valid token (within the same session).
Should this be the case or am I using it wrong? I would have expected a used csrf token to become invalidated (so it can only be used once per session id).
My code looks something like this:
var express = require('express');
var bodyParser = require('body-parser');
var csurf = require('csurf');
app.use(csurf());
app.use(function (req, res, next) {
res.locals.csrfToken = req.csrfToken();
next();
});
According to OWASP: "Synchronizer (CSRF) Tokens (are) Unique per user session" which I take to mean that they are tied to the session. This means that it is possible to re-use a token from the same session. Hence the answer to the question is 'yes'.
It seems then that npm csurf is using a standard best practice by allowing the re-use of csrf tokens within the same session (even after they have been consumed once).
Relevant part from OWASP:
Synchronizer (CSRF) Tokens
Any state changing operation requires a secure random token (e.g.,
CSRF token) to prevent CSRF attacks
Characteristics of a CSRF Token
Unique per user session
Large random value
Generated by a cryptographically secure random number generator
The CSRF token is added as a hidden field for forms or within the URL
if the state changing operation occurs via a GET The server rejects
the requested action if the CSRF token fails validation

using csurf with session in express

I'm writing a single page application with MEAN stack, and using express-session with redis for session management.
I want to use scrf token in my client's cookies.
the problem is when I add csurf middleware, it set a session with name csrfSecret in redis, but how can I send it in cookie to client?
middlewares :
app.use(csrf({}));
app.use(function(req, res, next) {
res.cookie('csrf-token', req.csrfToken());
return next();
});
and csrf-token is sending to client but it don't do anything.and I receive 403 error from module.
thank you for any answer or idea.
If you want to create a csrf cookie in the client you have to use the following:
app.use(csrf({ cookie: true })
This will create a token in the client. If you do not pass any options to the csrf function it will use req.session. If you want to save the cookie client-side, remember that you will need to use cookie-parser module.
You can find more information in the github link to the project: https://github.com/expressjs/csurf

Using cookieParser() and cookieSession() together?

cookieParser() gives us the option of signing cookies with a secret sentence, which is great to prevent tampering. I understand that a cookie is signed with a special value, to prevent tampering.
I just discovered cookieSession(), which I find to be a great alternative to server-stored cookies (I only store { loggedIn = true, userId=763487246824632}, it never grows).
But... I found that setting a "secret" for cookieParser() breaks things, and cookieSession() stops working if the secret sentence matches.
The reason seems to be that if the cookie is signed using the same secret, then cookieParser() actually takes it and parses it. The strange thing is that once cookieParser() has done its work, and with the same signature secret, the session is set to:
{ cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true } }
Rather than:
{ testing: 'OOO' }
(Each reload adds an 'o')
So...
Did my analysis get it right?
Do you know why the session is set to that strange { cookie object if the secret sentences match?
Merc.
Your analysis is correct, I can reproduce it.
The issue is caused by this line in the cookieSession middleware (some context: options.secret is the key passed to cookieSession, req.secret is the key passed to cookieParser): if you pass both middleware a secret key, cookieSession assumes that it will find the raw (unparsed) cookie in req.cookies.
But since cookieParser has picked up the signed cookie as well (and it's being run before cookieSession), it has parsed the cookie itself (and because the signing keys were the same, it succeeded to do so), stored it in req.signedCookies and deleted it from req.cookies. So as far as cookieSession is concerned, the cookie just isn't set.
The object you see is the default session contents (which is the cookie property from the cookieSession configuration):
app.use(express.cookieSession({
cookie : { // <-- this object
...
}
});
As for a solution: either use a different key for each middleware, or just pass one of them your secret key, but not both (with the understanding that if you pass it to cookieParser, all your cookies will be signed).
FWIW: I'm not entirely sure if this is a real bug. It's a consequence of using the same signing mechanism for both cookieParser and cookieSession, with no distinction between cookies signed by one or the other. Although it could be fixed by always checking if the cookie is located in req.signedCookies.

Resources