Bad CSRF token with AngularJS and Express - angularjs

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.
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.

if anyone gets "csurf configuration error", try this:
app.use( csrf( { cookie: true } ));

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.

Related

req.body empty Node.js

this is my angular controller code where im passing certificationid and userid to delete certification details of a user.
$scope.deleteCertification = function(CertificationId){
var userName = $scope.userId;
var certificationId = CertificationId;
var deleteCertificationInfo = {'userName': userName, 'certificationId':certificationId};
console.log('deleteCertificationInfo*******');
console.log(deleteCertificationInfo);
userProfileService.deleteUserCertificationInfo(deleteCertificationInfo).then (function(data){
console.log($scope.Certification);
console.log('Certification Deleted');
})
}
userProfileData.deleteUserCertificationInfo = function (deleteCertificationInfo) {
var deferred = $q.defer();
$http.delete('/api/profileUpdate/deleteUserCertification', deleteCertificationInfo, {
}).success(function(res){
var deletedUserCertificationResult = res;
deferred.resolve(deletedUserCertificationResult);
$log.debug('response from certification API:['+JSON.stringify(deletedUserCertificationResult)+']');
}).error(function(err){
deferred.reject(err);
});
return deferred.promise;
};
that is written in userProfileService to call the delete API.
but in my node controller function req.body is empty. not sure where it is going. im consoling the data in front end before sending it to service . it's displayed then. but why the req.body is getting empty?
Even though you haven't posted the Express portion of your app, the best guess here is that you're not using body-parser. body-parser is an Express middleware that is required when using req.body, without adding it to your Express app, you won't be able to parse any incoming JSON or url-encoded request bodies.
const express = require('express');
const bodyParser = require('body-parser');
const port = process.env.PORT || 3000;
let app = express();
app.use(bodyParser.json()); // this will parse Content-Type: application/json
app.use(bodyParser.urlencoded({ extended: true })); // this will parse Content-Type: application/x-www-form-urlencoded
// Your routes go here
app.listen(port);
try with the follwing code, its worked for me , you shoud have this code in your node service js file
app.use(bodyParser.json()); // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
extended: true
}));

Express/Angular/Browsersync CORS - No 'Access-Control-Allow-Origin' 403 (forbidden)

I'm as junior as it gets when it comes to web development so please bear with me on this. I'm attempting to access data from the USDA Food Composition Databases NDB API - https://ndb.nal.usda.gov/ndb/doc/index
via an angular $http request from localhost. I'm using an express server and gulp/browsersync and am encountering two errors:
Failed to load resource: http://api.nal.usda.gov/ndb/list?format=json&It=f&max=20&sort=n&offset=15&api_key=API_KEY the server responded with a status of
and
XMLHttpRequest cannot load http://api.nal.usda.gov/ndb/list?format=json&It=f&max=20&sort=n&offset=15&api_key=API_KEY. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access. The response had HTTP status code 403.
I've tried setting my CORS headers in browsersync as well as my express server but I simply cannot get around this issue. Here is how I've configured the relevant code for this:
The $http request
(function() {
'use strict';
angular
.module('commonSenseDietApp')
.factory('getFoodNamesOnly', getFoodNamesOnly);
/** #ngInject */
function getFoodNamesOnly($log, $http, devEnvironment) {
var service = {
ndbApiKey: devEnvironment.api_key,
ndbApiUrl: devEnvironment.api_url,
getFoodNamesList: getFoodNamesList
};
return service;
function getFoodNamesList(limit) {
if(!limit) {
limit = 30;
}
// For a list of all request parameters visit - https://ndb.nal.usda.gov/ndb/doc/apilist/API-LIST.md
return $http.get(service.ndbApiUrl + '/ndb/list?format=json&It=f' + '&max=' + limit + '&sort=n&offset=15&api_key=' + service.ndbApiKey)
.then(returnFoodNamesList)
.catch(getFoodNamesFail);
function returnFoodNamesList(response) {
return response.data;
}
function getFoodNamesFail(err) {
// return $log.error(err.data);
return console.log(err);
}
}
}
})();
My Browersync/Express Server
'use strict';
var express = require('express');
var cors = require('cors');
var bodyParser = require('body-parser');
var http = require('http')
// require database data modeling via mongoose
var mongoose = require('mongoose');
var session = require('express-session');
var cookieParser = require('cookie-parser');
var flash = require('connect-flash');
// Use express and set it up
var app = express();
app.use(cors());
app.use(function (req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
res.setHeader('Access-Control-Allow-Credentials', false);
next();
});
app.set('views', __dirname + '/views');
app.use(express.static(__dirname + '/'));
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json())
var path = require('path');
var gulp = require('gulp');
var conf = require('./conf');
var browserSync = require('browser-sync');
var browserSyncSpa = require('browser-sync-spa');
var util = require('util');
var proxyMiddleware = require('http-proxy-middleware');
function browserSyncInit(baseDir, browser) {
browser = browser === undefined ? 'default' : browser;
var routes = null;
if(baseDir === conf.paths.src || (util.isArray(baseDir) && baseDir.indexOf(conf.paths.src) !== -1)) {
routes = {
'/bower_components': 'bower_components'
};
}
var server = {
baseDir: baseDir,
routes: routes,
middleware: function (req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With, content-type');
// to the API (e.g. in case you use sessions)
res.setHeader('Access-Control-Allow-Credentials', false);
next();
}
};
browserSync.instance = browserSync.init({
startPath: '/',
cors: true,
browser: browser,
notify: true,
port: 8080,
server: server,
});
}
browserSync.use(browserSyncSpa({
selector: '[ng-app]'// Only needed for angular apps
}));
gulp.task('serve', ['setenvconstants','watch'], function () {
browserSyncInit([path.join(conf.paths.tmp, '/serve'), conf.paths.src]);
});
gulp.task('serve:dist', ['setenvconstants','build'], function () {
browserSyncInit(conf.paths.dist);
});
gulp.task('serve:e2e', ['inject'], function () {
browserSyncInit([conf.paths.tmp + '/serve', conf.paths.src], []);
});
gulp.task('serve:e2e-dist', ['build'], function () {
browserSyncInit(conf.paths.dist, []);
});
My Angular .config
(function() {
'use strict';
angular
.module('commonSenseDietApp')
.config(config);
/** #ngInject */
function config($logProvider, $httpProvider) {
// Enable log
$logProvider.debugEnabled(true);
// For Access-Control-Allow-Origin and Set-Cookie header
$httpProvider.defaults.withCredentials = false;
}
})();
I'm using gulp and browsersync to serve locally over localhost:8080 but no matter what I try (setting headers in express, setting headers in browsersync, setting browsersync cors option to 'true', setting browsersync https options to true, switching my 'Access-Control-Allow-Origin' to '*' or to "localhost:8080") none of it seems to work. I suspect the NDB API has forbidden my access but I can't get in contact with them to ask about it. Their suggested contact us link - "https://api.data.gov/contact/" leads to nothing.
Any suggestions or tips on this would be greatly appreciated. I'm a total noob here in terms of web development as well as posting to Stack Overflow so please let me know if my question doesn't make any sense and needs further clarification.
I was fortunate enough to stumble upon a solution although I don't quite understand what's happening and would certainly like to.
Turns out I was attempting to run a local server while using my VPN (https://www.privateinternetaccess.com/) which for some reasons was causing my CORS issue. Once I turned the VPN off and began using my local network I was able to run my server and make my requests without a hitch.
I'm not sure why using my VPN would cause a 403 but my guess would be that the API I was attempting to access simply does not allow request from a remote network like the one I was using. I will look into it more and update my answer shortly.
Try serving from https and not http when making your API calls. Being that you are fetching an https location, but issuing an http request, you will get CORS issue.
Look into: https://nodejs.org/api/https.html

How to enable CORS on node js?

I am having a problem that I don't really understand. I have a node js server that server simple index.html page (This is actually angular). My server code looks like this:
var express = require('express');
var app = express();
var cors = require('cors')
var port = 4000;
var path = require("path");
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.use(express.static('.'))
console.log(__dirname + '/.');
app.use(cors({
origin: true,
credentials: true
}));
app.get("/", function(res, req){
req.sendFile(path.join('/index.html'));
});
app.listen(port,'0.0.0.0' , function(){
console.log("listening on * "+ port);
});
I my html page, I have and angularjs service that is accessing localhost:7000 and socket.io that is accessing localhost:7000.
My service look like this :
if(param){
$scope.isloading = true;
$http.post(
'http://' + $location.host() + ':7000/container',
{ "method": "start", "nomber": param } ,
{}
).then(function(err, data){
console.log("No error : ");
console.log(data);
if (err){
console.log(err);
}
$scope.isloading = false;
console.log("end Loading" + $scope.isloading);
}, function(err, data){
$scope.isloading = false;
console.log("error ");
});
}
and the html call to socket.io is this :
<script>var socket = io.connect('http://localhost:7000');
socket.on("news", function(data){
console.log(data);
});</script>
my problem is that I am unable to allow the angular service and socket.io call at the same time. I have installed CORS on chrome. when I enable it, socket.io don't work, but service works.. When I disable it service don't work and socket.io does: . Can you please help me to find a solution ?
Update
I have tried many of the solution proposed here. But they don't work for me.
Try like this,
app.use(cors({
origin: function (origin, callback) {
var allowed = ['http://localhost:7000'].indexOf((origin || '').toLowerCase()) !== -1;
callback(null, allowed);
}
}));
Since the error message says:
A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin'
header when the credentials flag is true
Why don't you try setting your origin instead?
I would use the following middleware:
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "http://localhost:4000");
next();
});
This is assuming that the credentials flag is absolutely necessary.

Angular Form Submit Post to Database

I am trying to post my form data to my database. When I click the submit button nothing happens. It should be calling my addDiscovery method in the discoverCtrl, but it just does nothing. I am getting no errors and have watched several tutorials in which (to me) I don't see the difference in my code and what is presented. Please tell this Angular noob what he is doing wrong!...Thanks in advance.
discoverCtrl.js:
angular.module('app')
.controller('discoverCtrl', ['$scope', '$http', function($scope, $http, $log) {
$scope.title = "Discover It!";
$scope.objects = ['animal', 'art', 'food', 'landform', 'object', 'plant', 'pokemon', 'vehicle'];
this.discovery = {};
$scope.addDiscovery = function() {
this.discovery.image = "";
this.discovery.location = ""/*gps api call*/;
this.discovery.discoveredOn = Date.now();
console.log("I'm posting something maybe?");
$http.post('/discover', {discovery: this.discovery}).success(function(data) {
});
this.discovery = {};
};
}]);
discover.html:
<h1>{{title}}</h1>
<form name="discoverForm" ng-controller="discoverCtrl" ng-submit="discoverForm.$valid && discoverCtrl.addDiscovery" novalidate>
<input ng-model="discoverCtrl.discovery.name" type="text" class="form-control" placeholder="What is the name of your discovery?" title="name" required />
<select ng-model="discoverCtrl.discovery.objectType" class="form-control" ng-options="object for object in objects" title="objectType" required>
<option value="" disabled selected>Select what type of discovery you have found.</option>
</select>
<textarea ng-model="discoverCtrl.discovery.description" class="form-control" placeholder="What does your discovery look/feel/smell like? Are there any other observations or knowledge you can share about it?" title="description" required></textarea>
<!--location found by gps TODO-->
<div>Discovery is {{discoverForm.$valid}}</div>
<input type="submit" ng-click="discoverCtrl.addDiscovery" class="btn btn-primary pull-right" value="Discover It!" />
</form>
server.js (sorry for the length. The route is at the bottom, but I didn't know if it could have been something else in here as well so I wanted to include it.):
// requires express and body-parser
var express = require('express');
var bodyParser = require('body-parser');
var expressSessions = require('express-session');
var logger = require('morgan');
var passport = require('passport');
// Google oauth2 login, key, and secrets
var GoogleStrategy = require('passport-google-oauth20').Strategy;
var configAuth = require('./auth');
// OAuth 2.0-based strategies require a `verify` function which receives the
// credential (`accessToken`) for accessing the Facebook API on the user's
// behalf, along with the user's profile. The function must invoke `cb`
// with a user object, which will be set at `req.user` in route handlers after
// authentication.
passport.use(new GoogleStrategy(configAuth.googleAuth,
function(accessToken, refreshToken, profile, cb) {
console.log('access token is', accessToken)
return cb(null, profile);
}));
// Configure Passport authenticated session persistence.
//
// In order to restore authentication state across HTTP requests, Passport needs
// to serialize users into and deserialize users out of the session. In a
// production-quality application, this would typically be as simple as
// supplying the user ID when serializing, and querying the user record by ID
// from the database when deserializing.
passport.serializeUser(function(user, cb) {
cb(null, user);
});
passport.deserializeUser(function(obj, cb) {
cb(null, obj);
});
//requiring and setting up mongo database/collections
var mongojs = require('mongojs');
var databaseUrl = "discoverIt";
var collections = ["users", "discoveries"];
// creates a databse in mongo called scrape with two collections: articles and comments
var db = mongojs('discoverIt', ['users', 'discoveries']);
// lets us know if there is an error with the database if it doesn't turn on
db.on('error', function(err) {
console.log('Database Error: ', err);
});
// creating an instance of express
var app = express();
// assigning the port or using the PORT environment variable
var PORT = process.env.PORT || 3000;
// makes static content in assets accessible
app.use(express.static(__dirname + '/public'));
// // BodyParser interprets data sent to the server
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.text());
app.use(bodyParser.json({type: 'application/vnd.api+json'}));
// Use application-level middleware for common functionality, including
// logging, parsing, and session handling.
app.use(require('morgan')('combined'));
app.use(require('cookie-parser')());
app.use(require('body-parser').urlencoded({ extended: true }));
app.use(require('express-session')({ secret: 'keyboard cat', resave: true, saveUninitialized: true }));
// Initialize Passport and restore authentication state, if any, from the
// session.
app.use(passport.initialize());
app.use(passport.session());
// Define routes.
app.get('/', function(req, res) {
res.sendFile(__dirname + '/public/views/index.html');
});
app.get('/login',
function(req, res){
res.sendFile(__dirname + '/public/views/login.html');
});
app.get('/login/google',
passport.authenticate('google', {scope: 'profile'}));
app.get('/login/google/return',
passport.authenticate('google', { failureRedirect: '/login' }),
function(req, res) {
res.redirect('/profile');
});
app.get('/profile',
require('connect-ensure-login').ensureLoggedIn(),
function(req, res){
db.users.insert({
"email": "",
"avatar": req.user.photos[0].value,
"userName": req.user.displayName,
"discoveries": 0,
"edits": 0,
"confirms": 0,
"points": 0
});
res.send({ user: req.user });
});
app.post('/discover', function(req, res){
db.discoveries.insert({
"user": req.user.displayName,
"userId": req.user.id,
"image": req.body.discovery.discovery.image,
"name": req.body.discovery.discovery.name,
"objectType": req.body.discovery.discovery.objectType,
"description": req.body.discovery.discovery.description,
"location": req.body.discovery.discovery.location,
"discoveredOn": req.body.discovery.discovery.discoveredOn
});
//*********************************
//increase user discovery count
//**********************************
});
//******************************************************
//starts the server letting user know the PORT
app.listen(PORT, function(){
console.log("listening on port %d", PORT);
}); // end of app.listen
As #dustmouse said, I needed the ()...oops!

enabling cors in meanjs rest api server

I am creating an api (server side) based on Meanjs.org latest version (0.4.0) and i managed to pull off only the MEN part and create one in http://localhost:3000/api
as the frontend part i created an Angularjs in http://localhost:4000/
and then i run both application using (P)ackage (M)anager 2
I am trying to create a user by sending user credentials using $resource like this
angular.module('users').factory('AuthenticationResource', ['$resource',
function($resource) {
return $resource('http://localhost:3000/api/auth/signup', {}, {
post: {
method: 'POST'
}
});
}
]);
...
//In my controller
$scope.signup = function() {
AuthenticationResource.post($scope.credentials, function(response) {
$scope.authentication.user = response;
$state.go($state.previous.state.name || 'home', $state.previous.params);
});
};
While in my server side's express.js
'use strict';
var config = require('../config'),
express = require('express'),
...
cors = require('cors');
...
module.exports.initModulesServerRoutes = function(app) {
// Globbing routing files
config.files.server.routes.forEach(function(routePath) {
require(path.resolve(routePath))(app);
});
};
module.exports.initCorsOption = function(app){
app.options('*', cors());
};
module.exports.init = function(db) {
// Initialize express app
var app = express();
...
// Initialise Cors options
this.initCorsOption(app);
// Initialize modules server routes
this.initModulesServerRoutes(app);
...
return app;
};
I am using node cors package to enable cors and just do app.options('*', cors()); to enable pre-flight across-the-board
But when i am trying to do a POST to http://localhost:3000/api/auth/signup i can see that my user is being saved to the database just fine but it doesn't give me any response and chrome console is giving me this
XMLHttpRequest cannot load http://localhost:3000/api/auth/signup. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:4000' is therefore not allowed access.
What did i miss?
I think you are missing app.use before all your routes:
Only express:
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
If you are using npm cors:
app.use(cors());

Resources