51

I've got Passport setup to authenticate users stored in mongodb. Seems to work fine: authentication succeeds/fails appropriately and session variables get set. However, getting Passport to check for a session is failing. Something seems to be quite wrong in that the console.log statements I've added to the deserializeUser callback never see the light of day. I assume my problem is related to deserializeUser never being called. Anyone able to diagnose my misstep?

// Passport configuration
passport.serializeUser(function(user, cb){ cb(null, user.id) });
passport.deserializeUser(function(uid, cb){
  console.log("Trying to deserialize user: "+uid);
  User.findById(uid, function(err, user){
    cb(err, user);
  });
});
// auth strategy function
passport.use(new LocalStrategy({usernameField: 'email'},
  function(email, pass, done){
    User.findOne({email: email}, function (err, user) {
      if (err)
        return done(err);
      if (!user)
        return done(null, false, {message: "Couldn't find user"});
      var crypted = bcrypt.hashSync(pass, user.salt);
      if(user.hashpass != crypted)
        return done(null, false, {message: "Bad password"});
      return done(null, user);
    });
  }
));

passport.CreateSession =  function (req, res, next) {
  passport.authenticate('local', function(err, user, info){
    if(err || !user)
      return res.json({status: "Failure: "+err});
    req.logIn(user, function (err){
      if(err)
        return res.json({status: "Failure: "+err});
      return res.json({status: "Authenticated"});
    });
  })(req, res, next);
};

with the following in app.js:

app.post('/session', passport.CreateSession); // restify better
app.del('/session', passport.DestroySession);
app.get('/images', passport.CheckSession, routes.images);
dhilt
  • 18,707
  • 8
  • 70
  • 85
Alex Westholm
  • 898
  • 1
  • 7
  • 9

12 Answers12

52

For anyone else who is having this issue, take a look at this:

app.use(session({ 
    secret: 'something', 
    cookie: { 
        secure: true
    }}));

If you have cookie.secure set to true and you're NOT using SSL (i.e. https protocol) then the cookie with the session id is not returned to the browser and everything fails silently. Removing this flag resolved the problem for me - it took hours to realise this!

Dave Kerr
  • 5,117
  • 2
  • 29
  • 31
  • 7
    Dave, is session here "express-session"? can you make that clear. – Alexander Mills Jun 17 '15 at 21:01
  • This was the issue for me, and it took 2 work days to find this answer... Thanks! – Inbar Shani Feb 16 '17 at 09:57
  • Thank you very much. Same thing for me. Serialize user was being called, but never deserialize. No cookies present in the browser, and nothing in req.session.passport. Flipping this flag seemed to help. Even though locally it was working, on my prod instance which is running on Heroku and using their free SSL, it didn't want to set the secure cookie. – kshreve May 04 '17 at 13:02
29

If you are using the authenticate callback when you authenticate with passport you need to log the user in manually. It will not be called for you.

passport.authenticate('local', function (err, user) {
    req.logIn(user, function (err) { // <-- Log user in
       return res.redirect('/'); 
    });
})(req, res);
Rick
  • 12,606
  • 2
  • 43
  • 41
  • 1
    From passport's docs: `Note: passport.authenticate() middleware invokes req.login() automatically. This function is primarily used when users sign up, during which req.login() can be invoked to automatically log in the newly registered user.` – jbodily Sep 14 '16 at 15:56
  • 5
    You might want to recheck that, if you are doing auth manually it will not be called. – Rick Sep 14 '16 at 20:13
  • 1
    How does it relate to *deserializeUser never being called* issue? – Green Oct 10 '17 at 01:52
  • `console.log(err.message)` after `req.logIn(user, function (err) {` show: `Failed to serialize user into session` ? – alditis Nov 02 '17 at 13:01
  • 1
    In the passport docs about the custom callback in `authenticate`: `The callback can use the arguments supplied to handle the authentication result as desired. Note that when using a custom callback, it becomes the application's responsibility to establish a session (by calling req.login()) and send a response.` – bertonc96 Dec 10 '19 at 10:52
13

Have you use()'d passport.session() middleware? Like in this example:

https://github.com/jaredhanson/passport-local/blob/v1.0.0/examples/login/app.js#L91

That's what restores the session and calls deserializeUser, so it sounds like that may be missing.

dhh
  • 4,289
  • 8
  • 42
  • 59
Jared Hanson
  • 15,940
  • 5
  • 48
  • 45
  • Jared - thanks for helping out. Yep, I've got app.use(passport.session()); in app.js – Alex Westholm Jul 01 '12 at 04:29
  • This is node 0.8.0, express 2.5.10, and the latest passport, btw. – Alex Westholm Jul 01 '12 at 04:37
  • Hmm, and I'm assuming you have your sessions properly set up and persisted too? If so, I don't see anything obvious. Here's my suggestion: 1. Try running under node 0.6.x, to see if there's an incompatibility somewhere with 0.8. 2. Post a runnable, copy-n-paste gist, and I'll run it and see what I can find. – Jared Hanson Jul 01 '12 at 05:16
  • 1
    Jared - I'm using express.cookieParser(), express.session({secret: 'somethingsecret'}), and passport.session()... anything else I'm missing? Did try it with node 0.6.0 and no joy. If that session setup looks proper and you don't see anything else, I'll post a gist later on this evening. Thanks so much for your help - the node community really benefits from folks like yourself aiding users. – Alex Westholm Jul 01 '12 at 16:45
  • Gist is here: https://gist.github.com/3030629 ...I didn't include the user model in the gist, but it's a straightforward mongo document with a bcrypted password and email field. The execution gets nowhere near it, so I didn't think it was critical - if you disagree, let me know and I'll update. – Alex Westholm Jul 02 '12 at 02:39
  • I'm not having any troubles with your gist. `deserializeUser` gets called and the `CheckSession` function `next()`s as expected. Are you using cluster or load balancing? That could cause a request to bounce to a different server, and you'll need to switch to something besides the MemoryStore for sessions (see connect-redis). – Jared Hanson Jul 02 '12 at 05:45
  • Other than that, I'm out of ideas. Can you run the example in passport-local? If that works, I'd start stripping things down until you isolate the point that is going wrong. Let me know what you discover. – Jared Hanson Jul 02 '12 at 05:46
  • Jared, no cluser, no load balancer, and the passport-local example works perfectly. Very strange. I'll continue to poke around, and if I find something, I'll let you know. – Alex Westholm Jul 02 '12 at 22:30
  • Fixed it. Seems that my version of Mongoose won't allow me to access document fields from the strategy definition without using .get() ...not sure how that didn't throw an error somewhere, but that seems to get me up and running. +1 for looking into it! – Alex Westholm Jul 04 '12 at 02:51
  • 5
    Also, `passport.session()` should be below/after the `app.use(session…` – laggingreflex May 11 '14 at 00:51
10

Okay, this took me almost 3 days to solve, which seems to be a simple fix, at least in my case. I had to place:

app.use(require('cookie-parser'));

and

app.use(require('express-session')({
    secret: 'keyboard cat',
    resave: false,
    saveUninitialized: false
}));

BEFORE calling:

app.use(passport.initialize());
app.use(passport.session());

Note: These snippets are not my actual code, I have taken it from GitHub, but that is the gist of it.

Bashir
  • 2,057
  • 5
  • 19
  • 44
Sharukh Mastan
  • 1,491
  • 18
  • 17
4

In my case it was because I had this code:

app.get("/auth/google/callback", 
    passport.authenticate("google", { 
        failureRedirect: "/admin/login/?error",
    }),
    (req, res) => {
        return res.redirect("/admin/");
    },
);

Apparently in this case req.login() will not be called (which calls deserialize). I changed it to this and it worked:

app.get("/auth/google/callback", 
    passport.authenticate("google", { 
        successRedirect: "/admin/", 
        failureRedirect: "/admin/login/?error",
    })
);
LachoTomov
  • 3,312
  • 30
  • 42
  • 2
    i tried this and it didn't work but then i made a bunch of random changes and it still didn't work and then i tried this again and it worked... computer science moment i guess – Kelvin Wang Jan 17 '23 at 22:18
3

Today I faced the same issue, and in my case the cause was how the client side makes the API calls. And the following approach helped me to overcome the problem:

const doFetch(url, method = 'GET', body) {
  const headers = new Headers();
  headers.append('Content-Type', 'application/json');
  headers.append('Accept', 'application/json');
  headers.append('Cache', 'no-cache');
  const params = { 
    method, headers, 
    credentials: 'include' // The cookie must be sent!
  }
  if(body) {
    params.body = body;
  }
  return fetch(url, params);
}

After this, deserializeUser began to be called properly.

dhilt
  • 18,707
  • 8
  • 70
  • 85
  • 1
    This put me on the correct solution, in my case, using Axios, I had to make my call with `axios.get(route, {withCredentials: true})` – disperse Mar 23 '22 at 21:39
1

I fought the same problem the entire day. I had a custom callback set up to do authorizations (like at the bottom of http://passportjs.org/guide/authenticate/). It looks like Passport preprocesses all request objects before they even hit the other middleware (based on some logging statements I wrote). As part of that preprocessing, it places the deserialized user information into the request.user object. Unfortunately, the default behavior of passport.authenticate ('local', callback) seems to be to ignore any session or cookie data, and instead assume that the username and PW are being sent in the request object. What I did to fix this was to first check the request.user object, and if it existed, it meant that Passport had done its preprocessing and then I could call login directly. Here is my authorization function, which I use in place of passport.authenticate('local'):

function doAuth(request, response, next)
{ 

if (request.user) {
    request.logIn(request.user, function (err) {
        if (err) {
            console.log(err);
            response.status(500).json({message:
                    "Login failed. Auth worked. Nonsense"});
            return;
        }
        console.log("doAuth: Everything worked.");
        next();
    });
    return;
}

passport.authenticate('local', function(err, user, info) {
    if (err) {
        response.status(500).json({message: "Boo Passport!"});
        return; //go no further
        //until I figure out what errors can get thrown
    }
    if (user === false) {
        console.log("Authentication failed. Here is my error:");
        console.log(err);
        console.log("Here is my info:");
        console.log(info);
        response.status(500).json({message: "Authentication failed."});
        return;
    }
    request.logIn(user, function(err) {
        if (err) {
        console.log("Login failed. This is the error message:");
        console.log(err);
        response.status(500).json({message:"Login failed."});
            return;
        }
        console.log("doAuth: Everything worked. I don't believe it.");

        next();
    });
})(request, response, next);

 }
Realz Slaw
  • 3,138
  • 1
  • 24
  • 38
user3522492
  • 125
  • 10
  • Your explanation helped me a lot thanks! I have not applied your solution but you pointed out the problem though :D – Kevin Delord Aug 26 '17 at 02:08
0

in my case, I've placed

app.use(cookieSession({
  maxAge : 60*60*1000,
  keys : [key.cookieSession.key1]
}));

above

app.use(passport.initialize());
app.use(passport.session());

and it worked.

pudio
  • 41
  • 4
0

I open the google chrome with web-security or open incognite mode, it's working for me

web-security for linux /usr/bin/google-chrome-stable
--disable-web-security --user-data-dir=/home/jossnaz/.config/google-chrome/
Chris Catignani
  • 5,040
  • 16
  • 42
  • 49
0

In Heroku, all requests come into the application as plain http, but with the X-Forwarded-Proto header to know whether the original request was http or https.

That causes express to see non-ssl traffic and so it refuses to set a secure cookie when running on Heroku. You have to tell express to trust the information in that header by setting 'trust proxy'.

app.set('trust proxy', 1);

right before

app.use(session...

might work for you if you're having trouble with this on Heroku specifically. If your cookies/session are messed up then that could be why deserialize is not being called.

sammy
  • 283
  • 2
  • 9
-1

make sure the line of code app.use(express.static(path.join(__dirname, 'public'))); stays below the app.use(require('express-session')

Marshall
  • 11
  • 5
  • 1
    Why? This order doesn't matter for the issue - `deserializeUser` is not called either. Also, you get DB query on EVERY assets request. Are you sure you need to query session for your every `styles.css`? – Green Oct 10 '17 at 02:21
-4
passport.serializeUser(
    function(user, done){
        done(null, user.id);
});

passport.deserializeUser(
    function(id, done){
        User.findById(id, function(err, user){
            if(err){
                done(err);
            }
            done(null, user);
        });
});

**> try this one**
Amit
  • 66
  • 9