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?

+4


source to share


3 answers


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.

+12


source


if someone gets a "csurf config error" try this:



app.use( csrf( { cookie: true } ));

      

+1


source


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.

0


source







All Articles