4

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 ?

Community
  • 1
  • 1
jbltx
  • 1,255
  • 2
  • 19
  • 34

3 Answers3

12

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.

1 - Send XSRF-TOKEN cookie

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>

2 - Add a _csrf input in non-Angular forms

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.

jbltx
  • 1,255
  • 2
  • 19
  • 34
  • Is this solution also valid if your frontend and backend domain is different? For an instance my backend server is running on port 4200 and frontend is on 3000 – Rahul Arora Feb 03 '17 at 20:01
  • Yes, you can see this answer to may have a better idea : http://stackoverflow.com/a/33175322/2904349 – jbltx Feb 03 '17 at 20:48
  • Does that mean I will have to implement may be an interceptor to pass on the cookie value manually everytime to angular JS headers? – Rahul Arora Feb 04 '17 at 07:19
  • As said in the AngularJS documentation, it will check automatically if the cookie exists and set it as HTTP header during XHR request (using $http, no need interceptor). If you need to make requests without using $http, you can pass $csrf everytime in the header, or make an interceptor. – jbltx Feb 05 '17 at 15:45
  • Thank you! Your answer really helped. Cheers! – Rahul Arora Feb 05 '17 at 16:58
1

if anyone gets "csurf configuration error", try this:

app.use( csrf( { cookie: true } ));
Spock
  • 2,482
  • 29
  • 27
0

I have created repository that you can directly use.

node

https://github.com/nil4you/node-csrf-and-http-only-cookie-with-spa

angular

https://github.com/nil4you/angular-csrf-and-httponly-cookie-demo

ask any question here any comment, as I can't see much coding issue, it's all about right setup issue.

Community
  • 1
  • 1
nirmal
  • 2,143
  • 1
  • 19
  • 29