I would like to add a CSRF protection in my app which uses a MEAN stack.
I tried the answer already given by someone : CSRF Protection in ExpressJS
But it was for the older express version, so I made some changes :
app.use(cookieParser(config.cookieSecret, { httpOnly: true }));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({ secret: config.sessionSecret, resave: false, saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());
app.use(csrf({value: function(req) {
var token = (req.body && req.body._csrf)
|| (req.query && req.query._csrf)
|| (req.headers['x-csrf-token'])
|| (req.headers['x-xsrf-token']);
return token;
}
}));
app.use(function(req, res, next) {
res.cookie('XSRF-TOKEN', req.csrfToken());
next();
});
I can see the token named XSRF-TOKEN
is well generated on my app (using Chrome inspection).
But when I post a form (Angular frontend), I have an error about the token :
{"message":"invalid csrf token","error":{"expose":true,"code":"EBADCSRFTOKEN","statusCode":403,"status":403}}
Did I miss something ? I'm wondering if req.csrfToken()
generates the good token given by Angular...
EDIT :
I just see the XSRF-TOKEN
is used by AngularJS in $http
requests only. So I think I have to add a hidden input in my form to post with csrf value, to be checked by Express, but how ?
Finally I was able to send the good token value. Here's the complete answer.
AngularJS uses CSRF protection (called XSRF by Angular) during requests made with $http
service.
When performing XHR requests, the $http service reads a token from a cookie (by default, XSRF-TOKEN) and sets it as an HTTP header (X-XSRF-TOKEN). Since only JavaScript that runs on your domain could read the cookie, your server can be assured that the XHR came from JavaScript running on your domain. The header will not be set for cross-domain requests.
There are many posts which explained how to send XSRF-TOKEN with ExpressJS 3.xx, but some things change with 4.xx version. Connect middlewares are not included anymore. Express uses its own middelware : cookie-parser, body-parser, express-session, and csurf.
The first step is to send the cookie, from the backend to the frontend (Express to Angular) :
var express = require('express');
var app = express();
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var session = require('express-session');
var config = require('./lib/config'); //config.js file with hard-coded options.
var csrf = require('csurf');
app.use(cookieParser(config.cookieSecret, { httpOnly: true }));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({
name: 'sessionID',
secret: config.sessionSecret,
cookie: {
path: '/',
httpOnly: true,
secure: false,
maxAge: 3600000
},
rolling: true,
resave: false,
saveUninitialized: true
}));
app.use(csrf());
app.use(function(req, res, next) {
res.cookie('XSRF-TOKEN', req.csrfToken());
next();
});
Now Angular is able to set its HTTP header (X-XSRF-TOKEN) during $http requests. Example :
<body ng-app="testApp">
<div ng-controller="LoginCtrl">
<form role="form" ng-submit="login()" >
<input type="email" ng-model="user.email" />
<input type="password" ng-model="user.password" />
<input type="submit" value="Log In" />
</form>
<p style="color:red">{{loginMsg}}</p>
</div>
</body>
<script>
var app = angular.module('testApp', ['ngResource']);
app.controller('LoginCtrl', function ($scope, $http) {
$scope.user = {};
$scope.loginMsg = '';
$scope.login = function () {
$http.post('/login', $scope.user).success(function () {
$window.location.href='/profile';
}).error(function () {
$scope.loginMsg = 'Wrong credentials, try again.';
});
};
});
</script>
That was my issue, I didn't know how to add this input. Finally I created a new Angular service named $csrf
:
app.factory('$csrf', function () {
var cookies = document.cookie.split('; ');
for (var i=0; i<cookies.length; i++) {
var cookie = cookies[i].split('=');
if(cookie[0].indexOf('XSRF-TOKEN') > -1) {
return cookie[1];
}
}
return 'none';
});
I made an example on Plunker : http://plnkr.co/edit/G8oD0dDJQmjWaa6D0x3u
Hope my answer will help.
if anyone gets "csurf configuration error", try this:
app.use( csrf( { cookie: true } ));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With