6

I'm trying to integrate passport into my nodejs server using connect, but can't seem to do it properly. All the guides/examples use expressJS, so I tried my best to reformat the code to work with my code, but I can't seem to get it to work. The related parts are written below. Does anybody have any advice on what might be the problem? passport.authenticate() never seems to be called (at least the console.log message within the facebook authentication callback never prints). I'm currently not saving anything to a database, so the issue should hopefully be something really simple that I'm just missing.

The only thing that comes to mind is the potential callback I have for facebook, which is a localhost url (since i'm still developing this locally). I was able to authenticate with facebook just fine using everyauth (from a purely local instance), but switched to passportJS since this I was having different issues there that passportJS seemed to address.

passport = require('passport');
  fpass = require('passport-facebook').Strategy;

passport.serializeUser(function(user,done){
    done(null, user);
});
passport.deserializeUser(function(obj,done){
    done(null,obj);
});

passport.use(new fpass({
        clientID:'facebook app id',
        clientSecret:'facebook app secret',
        callbackURL:'http://localhost:3000/auth/facebook/callback'
    },
    function(accessToken, refreshToken, fbUserData, done){
        console.log('got here');
        return done(null,fbUserData);
    }
));


    
    function checkLoggedIn(req, res, next){
        console.log("req.user: " + req.user);
        if(req.user)
            next();
        else{
            console.log('\nNot LOGGED IN\n');
            if(req.socket.remoteAddress || req.socket.socket.remoteAddress == '127.0.0.1'){
                var folder,contentType;
                console.log('req url = '+req.url);
                if(req.url == '/'){
                    folder = __dirname + '/landingPage.html';
                    contentType = 'text/html';
                }
                else if(req.url == '/auth/facebook'){
                    passport.authenticate('facebook');
                    return;
                }
                else if(req.url == '/auth/facebook/callback'){
                    passport.authenticate('facebook', {failureRedirect: '/failbook', successRedirect:'/'});
                    return;
                }
                if(folder){
                    console.log('got to folder part\n\n');
                    fs.readFile(folder, function(error, content){
                      if(error){
                        res.writeHead(500);
                        res.end();
                      }
                      else{
                        res.writeHead(200, {'Content-Type': contentType});
                        res.end(content);
                      }
                    });
                  }
                    else{ res.writeHead(500); res.end();}
            }
            else {res.writeHead(500); res.end();}
        }
    }
    
  connect.createServer(
    connect.cookieParser(),
    connect.bodyParser(),
    connect.session({secret:'wakajakamadaka'}),
    passport.initialize(),
    passport.session(),
    checkLoggedIn).listen(8888);
  console.log('Server has started.');
}

Does anybody have any advice or see a glitch in what I'm doing? My other two alternatives are switching back to everyauth and figuring out what's going on there, or switching to ExpressJS, but I'd rather not go with either of those options.

General Grievance
  • 4,555
  • 31
  • 31
  • 45
thisissami
  • 15,445
  • 16
  • 47
  • 74

1 Answers1

8

passport.authenticate returns a function which is typically used in the middleware chain, which gets invoked with req and res by Express.

It will work standalone inside other middleware or a route handler, such as your checkLoggedIn function, but you have to explicitly invoke the function returned with req, res, and next to let it process the request.

else if(req.url == '/auth/facebook'){
  // NOTE: call the function returned to process the request
  passport.authenticate('facebook')(req, res, next);
  return;
}

Looks good otherwise. Let me know if that gets you up and running.


UPDATE

Jared has helped me a little bit outside of stackoverflow, and we have figured out the problem fully. I'm updating this answer to include the new information we found.


1) One issue is clearly that the redirect() middleware exists in Express but not in Connect (at least not the current version). When using connect, you'll need to change line 97 of authenticate.js in the PassportJS module from return res.redirect(options.successRedirect); to:

var redirect = function(redirectionURL){
    res.writeHead(302, {'location':redirectionURL});
    res.end();             
}

return redirect(options.successRedirect);

note - the options.successRedirect above is the same as the successRedirect in the callback function code. This would be the line passport.authenticate('facebook', {failureRedirect: '/failbook', successRedirect:'/'}); in the code in my question.


2) The other is that I'm using the cluster module in my app (not something that shows in my code snippet above). This means that session data isn't being read properly by the nodeJS app, which resulted in my problems. This problem will affect any library that relies on sessions, including all other authentication modules that use sessions (e.g. everyauth, connect-auth, etc.) I will quote Jared for a solution:

Quick reply: are you using cluster or spinning up multiple server instances?

If so, the built-in MemoryStore for the session won't work properly, and you need to switch to something like this: https://github.com/visionmedia/connect-redis

Or one of the other session stores listed here: https://github.com/senchalabs/connect/wiki

The reason is that you'll end up hitting a server which didn't serve the original request, and thus doesn't have that session information in its own memory. That sounds like what could be going on if deserializeUser isn't called 100% of the time.

Hope this helps whoever makes it here!

thisissami
  • 15,445
  • 16
  • 47
  • 74
Jared Hanson
  • 15,940
  • 5
  • 48
  • 45
  • hmm i'm getting farther than i was before, but it isn't quite working yet. now i'm registering the callback from facebook. I am checking '/auth/facebook/callback' against req.url.split('?')[0] now so that the callback will register. adding a (req,res,next) after the function call causes an infinite redirect loop. any ideas? – thisissami Jun 13 '12 at 02:54
  • I think you'll need to use `connect.query()` middleware in your `createServer` middleware setup. Passport expects `req.query` to be populated in a lot of cases, including getting the authorization code in the Facebook callback. – Jared Hanson Jun 13 '12 at 06:19
  • almost there... with a `(req,res,next)` added to the end of the callback authentication call, the facebook response function is reached with the proper facebook data. However, I get a `TypeError: Object # has no method 'redirect'`. The offending line of code is `return res.redirect(options.successRedirect);` on line 97 of authenticate.js. Any idea what the problem might be? – thisissami Jun 14 '12 at 02:51
  • clearly the problem is that connect doesn't have a redirect() middleware anymore, whereas express does. i've replaced that line with my own returned function that achieves the same thing. facebook data works properly now, and the standard '#_=_' that facebook returns reaches the browser. however, req.user is still undefined on future requests. any idea as to what might be causing that? do i have to call serializeUser at any point in time? – thisissami Jun 14 '12 at 21:18
  • Passport should call your serializeUser/deserializeUser functions if everything is working properly. Can you add log statements there to verify if it is, as well as the arguments that it gets. Also, I'm curious if `fbUserData` contains the user info after login. Let me know that stuff. Eager to get you up and running! – Jared Hanson Jun 15 '12 at 03:01
  • yep fbuserdata is definitely returning the right data. i have everything printing in my own personal programs - i just didn't include them above to conserve readability. it gets to authenticate just fine, the callback is received with the correct information, and facebook returns the correct forwarding url. i'll add prints to the serialization commands now to check on that and will get back to you. – thisissami Jun 15 '12 at 03:05
  • ok so adding those new checks verifies that stuff is definitely serializing. also, upon attempting to login enough times, it will occasionally work BRIEFLY. working briefly means it'll return the HTML page, but then all the stuff that page tries to load will fail. It seems like "deserialization" only works on those times when things are returned, and all other times nothing is printed. I wonder if the issue has to do with my session settings - do I need to do anything to make cookies work properly? – thisissami Jun 15 '12 at 03:13
  • I have a feeling the problem has to do with the new versions of Connect taking out code that PassportJS (and other auth modules) rely on. The issue I'm having now is identical to the issue I was having with EveryAuth that made me jump ship. – thisissami Jun 15 '12 at 03:22
  • For whoever gets infinite loop of attempting /auth/facebook/callback, remember to implement both res.redirect AND req.query. Connect doesn't have req.query and OAuth2Strategy.prototype.authenticate checks "req.query.code" – kmsheng Jul 18 '20 at 15:12