Express CSRF token validation
Asked Answered
D

4

10

I'm having issues with CSRF tokens. When I submit a form, a new XSRF-TOKEN is being generated but I think I'm generating two different tokens, I'm kinda confused. There's also a token called _csrf, so I see two different cookies in developer tools (XSRF-TOKEN and _csrf), _csrf doesn't change after a post.

What I want to do is to generate a new token for each post request and check whether it's valid or not. One thing I know that I should do it for security, but I stuck.

It has been a long day and I'm new into Express and NodeJS.

Here's my current setup.

var express = require('express')
  , passport = require('passport')
  , flash = require('connect-flash')
  , utils = require('./utils')
  , csrf = require('csurf')
  // setup route middlewares
  ,csrfProtection = csrf({ cookie: true })
  , methodOverride = require('method-override')
  , bodyParser = require("body-parser")
  , parseForm = bodyParser.urlencoded({ extended: false })
  , cookieParser = require('cookie-parser')
  , cookieSession = require('cookie-session')
  , LocalStrategy = require('passport-local').Strategy
  , RememberMeStrategy = require('../..').Strategy;


var app = express();

app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.engine('ejs', require('ejs-locals'));
app.use(express.logger());
app.use(express.static(__dirname + '/../../public'));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(methodOverride());
app.use(express.session({ secret: 'keyboard cat' }));
app.use(flash());
// Initialize Passport!  Also use passport.session() middleware, to support
// persistent login sessions (recommended).
app.use(passport.initialize());
app.use(passport.session());
app.use(passport.authenticate('remember-me'));
app.use(app.router);
app.use(csrf());

app.use(function (req, res, next) {
  res.cookie('XSRF-TOKEN', req.csrfToken());
  res.locals.csrftoken = req.csrfToken();
  next();
});

Routes

app.get('/form', csrfProtection, function(req, res) {
  // pass the csrfToken to the view
  res.render('send', { csrfToken: req.csrfToken()});
});

app.post('/process', parseForm, csrfProtection, function(req, res) {
  res.send('data is being processed');
});

send.ejs (/form GET)

<form action="/process" method="POST">
  <input type="hidden" name="_csrf" value="<%= csrfToken %>">

  Favorite color: <input type="text" name="favoriteColor">
  <button type="submit">Submit</button>
</form>
Dicky answered 10/10, 2015 at 23:29 Comment(2)
Please refer to: CSRF Configurations working with Cookies. This is working for me.Scabble
Don't put your CSRF token into the cookie.Shotputter
F
16

Based on the amount of code you shared, I will mention a few things that don't look quite right to me:

1 . You may need to swap the lines below so that csrf runs before the routes.

app.use(csrf());
app.use(app.router);

2 . The csrftoken setup needs to also be placed before the routes.

app.use(csrf());
app.use(function (req, res, next) {
  res.cookie('XSRF-TOKEN', req.csrfToken());
  res.locals.csrftoken = req.csrfToken();
  next();
});
app.use(app.router);

3 . You'll need to use locals.csrftoken in your form:

<form action="/process" method="POST">
  <input type="hidden" name="_csrf" value="<%= csrftoken %>">

  Favorite color: <input type="text" name="favoriteColor">
  <button type="submit">Submit</button>
</form>
Fazeli answered 10/10, 2015 at 23:34 Comment(6)
Done that, it gives ForbiddenError: invalid csrf token error now. I edited the question a little bit, hope it's more clear now.Dicky
I get TypeError: req.csrfToken is not a function error now. Shouldn't we use the app.use(csrf() before the app.use(function) part?Dicky
@Dicky you're right! csrf() should be bfore the app.use partFazeli
Now I'm back to invalid csrf token part. XCSF-TOKEN changes whenever I post the form or refresh the page (GET) while _csrf is the same. I think it's trying to get _csrf's value but I'm not sure.Dicky
@Dicky maybe you're running req.csrfToken() too many times. There should be only one token generationFazeli
Right, do not run req.csrfToken() twice... app.use(function (req, res, next) { var csrfToken = req.csrfToken(); res.cookie('XSRF-TOKEN', csrfToken); res.locals.csrftoken = csrfToken; next(); });Aria
B
0

the token in the cookie will be completely different than the one in the express session. you want to check for one or the other not both.

i would disable the cookies entirely! as it worked for me.

var csrfProtection = csurf({ cookie: false });

the author mentions it here https://github.com/expressjs/csurf/issues/52

next you want to the "X-CSRF-Token" to the header on ajax post found here: Express.js csrf token with jQuery Ajax

Bim answered 7/3, 2017 at 19:23 Comment(2)
Why would you use JQuery when you can set the X-CSRF-Token to cookies, headers or local variable just by using express?Ragouzis
the OP is trying to validate based on a form postBim
S
0

Below code is working for me. Let me know in case you still face issue.

As mentioned that you wish to use cookies, you have make csurf aware that you are using cookies for setting the CSRF token.

Step1: Configuration

var csrf = require('csurf');
var cookieparser= require('cookie-parser'); 

//cookieparser must be placed before csrf 
app.use(bodyparser.urlencoded({extended:false}));
app.use(cookieParser('randomStringisHere222'));
app.use(csrf({cookie:{key:XSRF-TOKEN,path:'/'}}));

//add the your app routes here
app.use("/api", person);
app.use("/", home);

Step2: In the route,

res.render('myViewPage',{csrfTokenFromServer:req.csrfToken()}); 

Step3: Include a hidden field in the HTML for csrf token Example:

<form action="/api/person" method="POST">
      <input type="hidden" name="_csrf" value=<%=csrfTokenFromServer %> />
      First name:<br>
      <input type="text" name="firstname" value="">
      <br>
      Last name:<br>
      <input type="text" name="lastname" value="">
      <br><br>
      <input type="submit" value="Submit">
 </form>
Scabble answered 16/3, 2017 at 19:13 Comment(0)
E
0

When we set csrf cookie it has default key _csrf. We can override it. So in my case I gave same name to cookie like this.

const csrf = csurf({cookie:{key:'XSRF-TOKEN'}});

app.get('/csrf-token', csrf, (req: Request, res: Response, next: NextFunction) => {
  const newToken = req.csrfToken();

  res.cookie('XSRF-TOKEN', newToken, {
    httpOnly: true,
    sameSite: 'strict',
    secure: process.env.NODE_ENV === 'production',
  });

  res.json({ csrfToken: newToken });
});

I dont know if you resolved the issue but its still will help if someone else looking for it.

Emmaemmalee answered 17/2, 2023 at 21:54 Comment(2)
This is a really bad practice to create an API to return only CSRF tokens in response.Sarabia
@KeyurChavda-kc1994 What is a good way ?Emmaemmalee

© 2022 - 2024 — McMap. All rights reserved.