auth0 / passport-linkedin-oauth2

Passport Strategy for LinkedIn OAuth 2.0
MIT License
119 stars 106 forks source link

problem with getting email address and other scope field in r_basciprofile #4

Closed biborno closed 10 years ago

biborno commented 10 years ago

Hi, I m very much new on this. it might ( i think it is) my mistake the problem i am facing. i m not been able to retrieve email adress or few other fields. infact only specif fields are return in the callback. here is my code: routing:

app.get('/auth/linkedin',passport.authenticate('linkedin', { scope: ['r_emailaddress','r_basicprofile','rw_nus'],state: 'DCEEFWF45453sdffef424' }));
  app.get('/auth/linkedin/callback',passport.authenticate('linkedin', { failureRedirect: '/' }),users.authCallback);

passport configuration:

var  LinkedInStrategy= require('passport-linkedin-oauth2').Strategy
passport.use(new LinkedInStrategy({
    clientID: config.linkedIn.clientID,
    clientSecret: config.linkedIn.clientSecret,
    callbackURL: config.linkedIn.callbackURL,

  },
  function(req,token, refreshToken, profile, done) {

    console.log(profile);

    if(!req.user)
    {

      User.findOne({ 'linkedIn.id': profile.id }, function (err, user) {
        //console.log(profile);
        if(err)
        {

          return done(err);
        }
        if(!user)
        {
          user=new User({
            linkedIn: profile._json
          });
user.save(function (err) {
            if (err) console.log(err)
            return done(err, user)
          });
      else
        {
          return done(err,user);
        }

      }); 
  }
));

here is console.log(profile):

{ provider: 'linkedin',
  id: 'LJitOAshpU',
  displayName: 'Monist BD',
  name: { familyName: 'BD', givenName: 'Monist' },
  emails: [ { value: undefined } ],
  _raw: '{\n  "firstName": "Monist",\n  "formattedName": "Monist BD",\n  "id": "
LJitOAshpU",\n  "lastName": "BD"\n}',
  _json:
   { firstName: 'Monist',
     formattedName: 'Monist BD',
     id: 'LJitOAshpU',
     lastName: 'BD' } }

now here is what i have done, first i tried this:

passport.use(new LinkedInStrategy({
    clientID: config.linkedIn.clientID,
    clientSecret: config.linkedIn.clientSecret,
    callbackURL: config.linkedIn.callbackURL,
    profileFields: ['id', 'first-name', 'last-name', 'email-address','public-profile-url'],
    passReqToCallback: true
  },
  function(req,token, refreshToken, profile, done) {

    console.log(profile);
}));

the result remain same. then i started look into passport-linkedin-oauth code, the scope i m passing from router is recieved here on /lib/oauth.js (on passport-linkedin-oauth2

Strategy.prototype.authorizationParams = function(options) {

  var params = {};
//this options print the scope and state passed from node route
//console.log(options) 
  // LinkedIn requires state parameter. It will return an error if not set.
  if (options.state) {
    params['state'] = options.state;
  }
  return params;
}

no matter from where i pass scope here print the same json fields that i recieved firstly.

Strategy.prototype.userProfile = function(accessToken, done) {

  //LinkedIn uses a custom name for the access_token parameter
  this._oauth2.setAccessTokenName("oauth2_access_token");

  this._oauth2.get(this.profileUrl, accessToken, function (err, body, res) {
    if (err) { return done(new InternalOAuthError('failed to fetch user profile', err)); }

    try {
      var json = JSON.parse(body);
     // i tried here sending different parameters from both configuration of passport and
//node route
      //console.log(json)
      var profile = { provider: 'linkedin' }; 

      profile.id = json.id;
      profile.displayName = json.formattedName;
      profile.name = { 
                        familyName: json.lastName,
                        givenName:  json.firstName
                     };
      profile.emails = [{ value: json.emailAddress }];
      profile._raw = body;
      profile._json = json;

      done(null, profile);
    } catch(e) {
      done(e);
    }
  });
}

the parameter I pass from passport configuration middle were is recieved here:

function Strategy(options, verify) {
//tried printing option here :P
//console.log(options);
  options = options || {};
  options.authorizationURL = options.authorizationURL || 'https://www.linkedin.com/uas/oauth2/authorization';
  options.tokenURL = options.tokenURL || 'https://www.linkedin.com/uas/oauth2/accessToken';
  options.scope = options.scope || ['r_basicprofile'];

  //By default we want data in JSON 
  options.customHeaders = options.customHeaders || {"x-li-format":"json"};

  OAuth2Strategy.call(this, options, verify);
  this.name = 'linkedin';
  this.profileUrl = 'https://api.linkedin.com/v1/people/~:(' + this._convertScopeToUserProfileFields(options.scope) + ')';
}

so i change my code into:

var  LinkedInStrategy= require('passport-linkedin-oauth2').Strategy
passport.use(new LinkedInStrategy({
    clientID: config.linkedIn.clientID,
    clientSecret: config.linkedIn.clientSecret,
    callbackURL: config.linkedIn.callbackURL,
    scope: ['r_emailaddress','r_basicprofile','rw_nus'],

  },
  function(req,token, refreshToken, profile, done) {

    console.log(profile);

    if(!req.user)
    {

      User.findOne({ 'linkedIn.id': profile.id }, function (err, user) {
        //console.log(profile);
        if(err)
        {

          return done(err);
        }
        if(!user)
        {
          user=new User({
            linkedIn: profile._json
          });
user.save(function (err) {
            if (err) console.log(err)
            return done(err, user)
          });
      else
        {
          return done(err,user);
        }

      }); 
  }
));

still I was not able to get other fields of basci profile such as public profile. then i found on function Strategy, this.profileUrl ._convertScopeTouserProfileFields is called

Strategy.prototype._convertScopeToUserProfileFields = function(scope) {

  var map = {
    'r_basicprofile':   ['id', 'first-name', 'last-name', 'picture-url', 'formatted-name'],
    'r_emailaddress':   'email-address'
  };

  var fields = [];

  if( scope.indexOf('r_basicprofile') === -1 )
  {
    scope.unshift('r_basicprofile');
  }

  scope.forEach(function(f) {
    if (typeof map[f] === 'undefined') return;

    if (Array.isArray(map[f])) {
      Array.prototype.push.apply(fields, map[f]);
    } else {
      fields.push(map[f]);
    }
  });

  return fields.join(',');
}

look on the map 'r_basicprofile': ['id', 'first-name', 'last-name', 'picture-url', 'formatted-name'] there is no public-profile-url fields. so i add it there:

Strategy.prototype._convertScopeToUserProfileFields = function(scope) {

  var map = {
    'r_basicprofile':   ['id', 'first-name', 'last-name', 'picture-url', 'formatted-name','public-profile-url'],
    'r_emailaddress':   'email-address'
  };

  var fields = [];

  if( scope.indexOf('r_basicprofile') === -1 )
  {
    scope.unshift('r_basicprofile');
  }

  scope.forEach(function(f) {
    if (typeof map[f] === 'undefined') return;

    if (Array.isArray(map[f])) {
      Array.prototype.push.apply(fields, map[f]);
    } else {
      fields.push(map[f]);
    }
  });

  return fields.join(',');
}

So, I dont think this is the right way to do it. on passport-linkedin there was profileFields that I can pass from passport configuration. but i have to switch in passport-linkedIn-oauth 2 bcoz using passport-linkedIn I was getting a token which is not supported for calling REST api of sharing in linkedIn. So, what I m not getting here. A lot of things I dont know. plz pardon me, if i m issuing something that was my code's error. i dont even know how to use test.

biborno commented 10 years ago

oh ! i just found that :( https://github.com/jschell12/passport-linkedin-oauth2 they are fixing bugs here, but when i run npm install passport-linkedin-oauth2 https://github.com/auth0/passport-linkedin-oauth2 this get installed ...my whole day gone in vain :'(

eugeniop commented 10 years ago

Hi @biborno, @jschell12 is proposing a more flexible approach to get additional profile fields. I'm not sure I get your problem though. Is is that you can't retrieve the e-mail? or you can't call the API? or both?

If the call to the API is failing with the access_token you get fro LinkedIn, very likely the issue is with the scope you specified. Can you share what API are you calling exactly?

I've tested again with the original implementation and I'm getting the email back when I specify the r_emailaddress scope. This is when logging in with my account in LinkedIn:

If I login with the r_emailaddress scope I see:

And then:

Dealing with identity is hard, and precisely one of the reasons we are working on Auth0 (our service): simplify connection with all identity systems like LinkedIn.

jfromaniello commented 10 years ago

@biborno you have to add scope to your constructor as follows:

var  LinkedInStrategy= require('passport-linkedin-oauth2').Strategy
passport.use(new LinkedInStrategy({
    clientID: config.linkedIn.clientID,
    clientSecret: config.linkedIn.clientSecret,
    callbackURL: config.linkedIn.callbackURL,
    scope:        [ 'r_basicprofile', 'r_emailaddress']
  },

I've added an example to this repository

biborno commented 10 years ago

thank u for fixing your code. can u tell me why in the _convertScopeToUserProfileFields only r_basicprofile,r_emailaddress and r_fullprofile have map? what about the other fields? @jfromaniello @eugeniop https://github.com/auth0/passport-linkedin-oauth2 i was using this library. i gave full example of my code. jfromaniello change code accordingly thank u.

anudeepjusanu commented 7 years ago

Hi @jfromaniello even after adding scope i m unable to get the email address its still showing undefined .get('/', passport.authenticate('linkedin', { scope: ['r_basicprofile', 'r_emailaddress'] }))

siacomuzzi commented 7 years ago

Make sure to enable r_emailaddress in your linkedin app settings:

image

Also, check the value of profile._json and try with different users.