Bad CSRF token with AngularJS and Express
I would like to add CSRF protection in my application that uses the MEAN stack.
I tried the answer already asked by someone: CSRF protection in ExpressJS
But that 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 that the token with the name is XSRF-TOKEN
well generated in my application (using Chrome validation).
But when I submit the form (Angular frontend) I have an error in 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 a nice token as provided by Angular ...
EDIT:
I just see that XSRF-TOKEN
AngularJS is being used in $http
requests only . So I think I need to add hidden input to my form in order to post the csrf value to be checked by Express, but how?
source to share
Finally, I was able to send a good token value. Here is the complete answer.
AngularJS uses CSRF protection (called XSRF by Angular) during requests made with the service $http
.
When making XHR requests , the $ http service reads the token from the cookie ( XSRF-TOKEN by default ) and sets it as the HTTP header ( X-XSRF-TOKEN ). Since only JavaScript that runs on your domain can read the cookie, your server can be sure that XHR came from JavaScript running on your domain. No header will be set for cross-domain requests.
There are many posts explaining how to send XSRF-TOKEN using ExpressJS 3.xx , but some things change with 4.xx version . Connect middlewares are no longer included. Express uses its own middleware : cookie-parser , body-parser , express-session, and csurf .
1 - Send XSRF-TOKEN file
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();
});
Angular can now 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>
2 - add _csrf input in Angular formats
This was my problem, 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 helps.
source to share
I've created a repository that you can use directly.
knot
https://github.com/nil4you/node-csrf-and-http-only-cookie-with-spa
corner
https://github.com/nil4you/angular-csrf-and-httponly-cookie-demo
ask any question here any comment as i don't see many coding problems, it's all about setting the problem correctly.
source to share