Search code examples
node.jsoauth-2.0sails.jslinkedin-api

SailsJS Linkedin OAuth 2.0 Login Flow Issues


I'm trying to use this library to authenticate using Linkedin:

https://github.com/auth0/passport-linkedin-oauth2

No Linkedin Login Prompt

I have configured my Passport Linkedin Strategy like so:

var passport = require('passport');
var LinkedInStrategy = require('passport-linkedin-oauth2').Strategy;

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

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

passport.use(new LinkedInStrategy({
  clientID: 'LINKEDIN_API_KEY',
  clientSecret: 'LINKEDIN_API_SECRET',
  callbackURL: 'http://localhost:1337/auth/linkedin/callback',
  scope: ['r_emailaddress', 'r_basicprofile'],
  state: true
}, function(accessToken, refreshToken, profile, done) {
  // asynchronous verification, for effect... 
  process.nextTick(function () {
    // To keep the example simple, the user's LinkedIn profile is returned to 
    // represent the logged-in user. In a typical application, you would want 
    // to associate the LinkedIn account with a user record in your database, 
    // and return that user instead. 
    return done(null, profile);
  });
}));

My AuthController.js looks like this:

var passport = require('passport');

module.exports = {

    login: function(req, res) {     
        passport.authenticate('linkedin', function(err, user, info) {
            // The request will be redirected to LinkedIn for authentication, so this 
            // function will not be called. 
        });
    },

    callback: function(req, res) {

        // ------------------------------------------------------------------------
        // after user authenticated, we get the user's email from
        // Linkedin's JSON response and save it against the matching  
        // email address in the User model
        // ------------------------------------------------------------------------
        console.log(res);
    },

    logout: function(req, res) {
        req.logout();
        res.send('logout successful');
    }   
};

From the linkedin oauth library, I expect the call to:

passport.authenticate('linkedin', function...);

In my AuthController's login action, to redirect the user to Linkedin's login prompt page but what I am actually seeing is my browser just keeps on loading, loading, loading and never stops.

Am I doing something wrong ?

Some questions I am not sure of:

  • Does Linkedin expect my server to be running on HTTPS before it lets this whole thing starts working ?
  • Is there some special configurations that I need to do in my Linkedin developers app setting ? (I've enabled all the correct Javascript SDK URLs)

Callback Error

OK, so continuing on, my next problem appears to be here:

return done(null, profile);
           ^
TypeError: object is not a function

My code is following the npm module instruction here: https://www.npmjs.com/package/passport-linkedin-oauth2

Maybe SailsJS has another way of writing it yet again....

Authentication Always Fails

After fixing the callback error as mentioned in my solution below, I decided to keep moving on and see how it goes despite the Linkedin documentation isn't quite matching 100% to what I expect from the NPM library.

My next problem is my authenticated.js policy appears to always fail.

My code is below:

// We use passport to determine if we're authenticated
module.exports = function (req, res, next) {
    if(req.authenticated) { // <---- this is the error line
        return next();
    }
    else
    {
        res.send(401, {
            error: 'Nice try buddy. Try logging in with Linkedin first :]'
        });
    }
};

Solution

  • No Login Prompt Solution

    sigh

    I think I'm beginning to grasp some of the difference between SailsJS and pure ExpressJS codes.

    The problem appears that I was missing this piece of code at the end of my passport.authenticate() method:

    (req, res)
    

    I picked it up after looking this tutorial again: http://iliketomatoes.com/implement-passport-js-authentication-with-sails-js-0-10-2/

    So now, the final authenticate method should look like:

    passport.authenticate('linkedin', function(err, user, info) {
    
    
    
        // The request will be redirected to LinkedIn for authentication, so this 
        // function will not be called. 
    
    
    
    })(req, res); // <--- notice this extra (req, res) code here
    

    Which matches the Passportjs documentation:

    passport.authenticate('local'),
      function(req, res) {
        // If this function gets called, authentication was successful.
        // `req.user` contains the authenticated user.
        res.redirect('/users/' + req.user.username);
      });
    

    In a way....if you know what I mean... :D

    Now I got my Linkedin login prompt as expected.

    Finally!

    Callback Error Solution

    OK.....I'm not sure if this is completes the login process...but....

    I noticed I had an extra line:

    passReqToCallback: true
    

    Taken from this page here:

    https://github.com/auth0/passport-linkedin-oauth2/issues/29

    I removed that and I got a different error message.

    I've also changed my callback code to look like:

    passport.authenticate('linkedin', function(err, user, info) {
    
        res.json(200, {
            user: user
        });
    
    })(req, res);
    

    and I got my user JSON which appears to be my Linkedin user profile info:

    {
        user: {
            provider: "linkedin",
            ...
        }
    }
    

    But that's...contradicting the Linkedin documentation...I don't see any access_token or expire_in properties which I was expecting to see in step 3 of the Linkedin OAuth 2.0 documentation (https://developer.linkedin.com/docs/oauth2)...

    So...supposedly...I should take this user object and create/update against an existing user object ?

    Authentication Always Fails Solution

    OK, so few more days, I added extra code to generate a User entity if one isn't found in my database, otherwise just return the found user.

    The was one last problem, in my policies folder, I have a authenticated.js and it looked like this:

    // We use passport to determine if we're authenticated
    module.exports = function (req, res, next) {
        if(req.authenticated) { // <---- this is the error line
            return next();
        }
        else
        {
            res.send(401, {
                error: 'Nice try buddy. Try logging in with Linkedin first :]'
            });
        }
    };
    

    Being new to all this web development stuff, I thought:

    req.authenticated; // should call match name of the file ?
    

    was correct but I was following this tutorial:

    http://iliketomatoes.com/implement-passport-js-authentication-with-sails-js-0-10-2/

    and he named his file: isAuthenticated.js I figured it's just a name....but I was wrong :D

    Turns out, the correct code was:

    req.isAuthenticated()
    

    So in full, the correct code becomes:

    // We use passport to determine if we're authenticated
    module.exports = function (req, res, next) {
        if(req.isAuthenticated()) { // alright, that's more like it!
            return next();
        }
        else
        {
            res.send(401, {
                error: 'Nice try buddy. Try logging in with Linkedin first :]'
            });
        }
    };
    

    Perhaps isAuthenticated is a Passportjs function and not just a name like I initially thought.

    My further research shows this page which suggests so to me:

    Problems getting Passport.js to authenticate user

    Maybe req.authenticated can only be used for HTML email-password login form as suggested in above Stackoverflow post and req.isAuthenticated() is for OAuth stuff.

    Anyhow, I still don't know if this is the right path but so far, I got authentication in my application now and I can access protected resources. Not sure how long I'll be logged in for, maybe I still need to build the refresh token thingo every 15 minute like the Linkedin documentation stated ?

    Hope this helps other fellow Sailsjs users who are facing the same problem :)