expressjs / csurf

CSRF token middleware
MIT License
2.3k stars 217 forks source link

csrf always fails #52

Closed maplesap closed 9 years ago

maplesap commented 9 years ago

The document says:

_csrf parameter in req.body generated by the body-parser middleware.

From the client, I have done a POST and the request payload is:

{"email":"testing@blah.com","password":"asdfas","_csrf":"Cn72pjW6-8PQ43dGXIAjslG488tFfAAgzX0s"}

But I always get a 403 error "session has expired or tampered with".

This is the expressjs app:

var express = require('express');
var app = express();
var http = require('http').Server(app);
var cookie = require("cookie-session");
var session = require("express-session");

app.use(cookie('keyboard cat'));
app.use(session({
 secret: 'keyboard cat'   
}));

var csrf = require('csurf');
var bodyParser = require('body-parser');

app.use(csrf());
app.use(function(err, req, res, next) {
    if (err.code !== 'EBADCSRFTOKEN') return next(err);
    res.status(403).json({"error": "session has expired or tampered with"});
});

app.use(express.static(__dirname + '/public'));
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

app.post("/register", function(req, res) {
    var email = req.body.email;
    var password = req.body.password;

    console.log('login done');
    console.log(req.body);
    res.json({"done":"done"});
});

app.get("/register", function(req, res) {
    res.json({"csrf": req.csrfToken()});
});

http.listen(3000, function() {
    console.log('Express app started');
});

The csrf token is retrieved when the client does a GET /register, which sets the csrf token on the form. When the form is submitted, the request payload is sent but denied by csurf. I'm still not clear on what is needed after reading the doc. Am I missing something?

dougwilson commented 9 years ago

You cannot use both cookie-session and express-session; they are mutually exclusive.

Simply remove one or the other and it works fine.

Your express-session is set up right, so if you remove the cookie-session, then it works. Your cookie-session is not setup correctly, so if you want to use that instead of express-session, then you'll need to fix that first.

maplesap commented 9 years ago

I removed the cookie-session, but it's still saying 403 Forbidden and that it's an invalid csrf token.

dougwilson commented 9 years ago

Make sure that the cookies that are set when you call register you include in your response with the token.

dougwilson commented 9 years ago

I'm pulling down and running your app now.

dougwilson commented 9 years ago

The issue is to use the _csrf token in the body, your app.use(csrf()); needs to be after any bodyParser uses, otherwise the body hasn't been parsed before the check occurs.

dougwilson commented 9 years ago

Clearly, the documentation needs to be expanded to explain that.

maplesap commented 9 years ago

Yes, that was the problem, thanks!

ricardograca commented 9 years ago

So, this means we can't use the "recommend way to use body-parser with express" anymore right?

dougwilson commented 9 years ago

@ricardograca yes you can; you just need to move down this middleware into each of your routes, after the body parser, rather than using this module globally (or don't pass the token in the body, but in the URL, a header, or somewhere else). Example:

var csrf = require('csurf')
var bodyParser = require('body-parser')
var express = require('express')
var session = require('express-session')

// setup body parsing
var parseJson = bodyParser.json()
var parseUrlencoded = bodyParser.urlencoded()

// create a group
var parseBody = [parseJson, parseUrlencoded]

// setup csrf
var csrfProtection = csurf()

// setup express
var app = express()

// i put this _before_ express-session to not invoke session
// lookups for static files
app.use(express.static(__dirname + '/public'))

// user sessions
app.use(session({
 secret: 'keyboard cat'   
}))

app.post('/register', parseBody, csrfProtection, function (req, res) {
    var email = req.body.email
    var password = req.body.password

    console.log('login done')
    console.log(req.body)
    res.json({"done":"done"})
})

app.get('/register', csrfProtection, function (req, res) {
    res.json({"csrf": req.csrfToken()});
})

// handle csrf errors specifically
app.use(function(err, req, res, next) {
    if (err.code !== 'EBADCSRFTOKEN') return next(err);
    res.status(403).json({"error": "session has expired or tampered with"});
});

app.listen(3000)
ricardograca commented 9 years ago

@dougwilson That makes sense, many thanks.

dougwilson commented 9 years ago

The readme should be clearer now :)