1

I need to handle persistent session on an Angular app using express and passport on the backend. After a successful login, if I make an http call (using angular $http) to an express API which returns request.isAuthenticated(), it always returns false. This is not case when I login and make the http call to the API using Postman, in that case i got true.

This is my configuration on the server:

server.js

const
        express = require('express'),
        config = require("../config"),
        path = require('path'),
        bodyParser = require('body-parser'),
        cookiePraser = require('cookie-parser'),
        cors = require('cors'),
        winston = require("winston"),
        morgan = require("morgan"),
        mongoose = require("mongoose"),
        passport = require("passport"),
        session = require("express-session"),
        flash = require("connect-flash"),



let app = express(),
    server = require("http").Server(app),
    io = require("socket.io")(server);

const sessionKey = "mySessionKey";




/*
 * ---------------------------------------------------------------------------------------
 * app configuration
 * ---------------------------------------------------------------------------------------
 */

// Add headers
app.use(function (req, res, next) {

    // Website you wish to allow to connect
    res.setHeader('Access-Control-Allow-Origin', req.headers.origin);

    // Request methods you wish to allow
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');

    // Request headers you wish to allow
    res.setHeader('Access-Control-Allow-Headers', "Content-Type,X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5,  Date, X-Api-Version, X-File-Name");

    // Set to true if you need the website to include cookies in the requests sent
    // to the API (e.g. in case you use sessions)
    res.setHeader('Access-Control-Allow-Credentials', true);

    // Pass to next layer of middleware
    next();
});



app.use(morgan("dev")); 
app.use(bodyParser.json({limit: "50mb"}));
app.use(cookiePraser(sessionKey));    
app.use(express.static("public"));



app.use(session({
    secret: sessionKey,
    resave: true,
    saveUninitialized: true,
    cookie: {
        secure: false,
        httpOnly: false
    }
}));
app.use(passport.initialize());
app.use(passport.session()); 

require("./passportConfig")(passport); // passport configuration


app.get("api/test", function(req, res){
    return json({isAuthenticated: req.isAuthenticated()});
})

// [..]

passportConfig.js

const   LocalStrategy   =   require("passport-local").Strategy,
        User            =   require("./models/User");



module.exports = function(passport) {


    // used to serialize the user for the session
    passport.serializeUser(function(user, done) {
        done(null, user.id);
    });

    // used to deserialize the user
    passport.deserializeUser(function(id, done) {
        User.findById(id, function(err, user) {
            done(err, user);
        });
    });





    passport.use('local-signup', new LocalStrategy({

            usernameField : 'email',
            passwordField : 'password',
            passReqToCallback : true // allows us to pass back the entire request to the callback
        },

        function(req, email, password, done) {

            // asynchronous
            // User.findOne wont fire unless data is sent back
            process.nextTick(function () {

                User.findOne({'local.email': email}, function (err, user) {
                    if (err)
                        return done(err);

                    // check to see if theres already a user with that email
                    if (user) {
                        return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
                    } else {

                        // if there is no user with that email
                        // create the user
                        let newUser = new User();

                        // set the user's local credentials
                        newUser.local.email = email;
                        newUser.local.password = newUser.generateHash(password);

                        // save the user
                        newUser.save(function (err) {
                            if (err)
                                throw err;
                            return done(null, newUser);
                        });
                    }

                });

            });

        }
    ));







    passport.use('local-login', new LocalStrategy({
            usernameField : 'email',
            passwordField : 'password',
            passReqToCallback : true // allows us to pass back the entire request to the callback
        },
        function(req, email, password, done) { // callback with email and password from our form

            // find a user whose email is the same as the forms email
            // we are checking to see if the user trying to login already exists
            User.findOne({ 'local.email' :  email }, function(err, user) {
                // if there are any errors, return the error before anything else
                if (err)
                    return done(err);

                // if no user is found, return the message
                if (!user)
                    return done(null, false, req.flash('loginMessage', 'No user found.')); // req.flash is the way to set flashdata using connect-flash

                // if the user is found but the password is wrong
                if (!user.validPassword(password))
                    return done(null, false, req.flash('loginMessage', 'Oops! Wrong password.')); // create the loginMessage and save it to session as flashdata

                // all is well, return successful user
                return done(null, user);
            });

        }
    ));





};

loginService.js (expose function for login):

this.login = function(email, password) {
        return new Promise((resolve, reject) => {

            $http({
                url: serverBaseURL + "/api/login",
                method: "POST",
         `enter code here`       data: JSON.stringify({
                    email: email,
                    password: password
                }),
                headers: {
                    "Content-Type": "application/json",
                    "Accept": "application/json"
                }    
            }).then(function (response) {
                resolve(response);
            }, function (errorResponse) {
                reject(errorResponse);
            });
        });
    };

In the controller I perform the login and then I make another http call to check if I am really logged in:

loginCtrl.js

loginService.login($scope.email, $scope.password).then(response => {
    if (response.status === 200) {

        // check if I am really logged in, include the connect.sid cookie in the call => withCredentials = true
        $http({
                url: serverBaseURL + "/api/test",
                method: "GET",
                headers: {
                    "Accept": "application/json"
                },
                withCredentials: true,

            }).then(function (response) {
                console.log(response);    // I always get false in the response
            }, function (errorResponse) {
                console.log(errorResponse);
            });
    }
}, error => {
    // handle error
});

// [..]

I've noticed two things:

  • In the first http call to "api/test" the connect.sid cookie is not included, even if I set withCredentials = true in the properties of $http
  • If I do a second call to "api/test", this time the connect.sid cookie is included in the request, but nevertheless I got always false in response (request.isAuthenticated() on the server side returns false).

Any idea what am I missing?

revy
  • 3,945
  • 7
  • 40
  • 85
  • Did you check whether `cookie` has been sent to server via `$http`? I think, by default `angular`'s `$http` doesn't send cookie information. You can pass `{ withCredentials: true }` to pass `cookie` along with the request. https://stackoverflow.com/questions/17064791/http-doesnt-send-cookie-in-requests – Mukesh Sharma Jun 13 '17 at 03:59
  • @MukeshSharma Thanks for the answer. Yes I forgot to mention that I am using withCredentials option in the request, see my edited question. I've noticed a strange thing: the first call to "api/test" does not include the cookie, while if I do a second call the cookie is included. In both cases I got false in response – revy Jun 13 '17 at 07:01

1 Answers1

0

I found this question because I have same situation getting always false from request.isAuthenticated() and status 401. Solution is as @Mukesh Sharma wrote is to don't forget to add on client-frontend side:

{ withCredentials: true }
Gajush
  • 56
  • 2
  • 7