Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using PassportJS with Connect for NodeJS to authenticate Facebook Users

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.

Best,
Sami

like image 627
thisissami Avatar asked Jun 12 '12 23:06

thisissami


1 Answers

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!

like image 142
Jared Hanson Avatar answered Sep 18 '22 02:09

Jared Hanson