passport-oauth2 icon indicating copy to clipboard operation
passport-oauth2 copied to clipboard

Profile info of user empty

Open codeeverystory opened this issue 8 years ago • 12 comments

I am connecting my node app to Microsoft Outlook using passport-oauth2 where I am experiencing some major error where it asks for permissions from the user and take me to redirectURL but it is not able to get me the profile info of user and return an empty object of profile.Below is some of my code i used:

passport.js

const OutlookStrategy=require('passport-oauth2').Strategy; passport.use(new OutlookStrategy({

authorizationURL: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', tokenURL: 'https://login.microsoftonline.com/common/oauth2/v2.0/token', clientID: configAuth.outlookAuth.clientID, clientSecret: configAuth.outlookAuth.clientSecret, callbackURL: configAuth.outlookAuth.redirectURL }, function(accessToken, refreshToken, profile, cb) { console.log(accessToken); console.log(profile); <-------------- This is empty object-------------------------| console.log(refreshToken); console.log(cb);

} ));

routes.js

router.get('/auth/outlook', passport.authenticate('oauth2',{ scope: outlookScope }) );

router.get('/auth/outlook/callback', passport.authenticate('oauth2',{ failureRedirect: '/' }), function(req, res) { // Successful authentication, redirect home. // var authCode = req.code; res.redirect('/account'); });

codeeverystory avatar Jan 11 '17 20:01 codeeverystory

It looks like there's no code for getting the profile at all, except we're free to override a method that's documented in the code but not in the README.

Really, OAuth2 doesn't seem to standardize how to get a profile, so it's not quite a fit for passport. The workaround I use is paraphrased below, but I have no idea if that's the right url for getting a profile.

let client = new OutlookStrategy({
    authorizationURL: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
    tokenURL: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
    clientID: configAuth.outlookAuth.clientID,
    clientSecret: configAuth.outlookAuth.clientSecret,
    callbackURL: configAuth.outlookAuth.redirectURL
},
function(accessToken, refreshToken, profile, cb) {
    // do stuff
});

client.userProfile = function (accesstoken, done) {
  // choose your own adventure, or use the Strategy's oauth client
  this._oauth2._request("GET", "https://login.microsoftonline.com/common/oauth2/v2.0/me/", null, null, accesstoken, (err, data) => {
    if (err) { return done(err); }
    try {
        data = JSON.parse( data );
    }
    catch(e) {
      return done(e);
    }
    done(null, data);
  });
};

passport.use(client);

knightcode avatar Apr 20 '17 20:04 knightcode

@knightcode your bit of code is a miracle worker I wish I had come across it much earlier.

danschroeder avatar Jan 16 '18 05:01 danschroeder

Found another workaround by passing an extra argument to the verify callback. There is some function arity check inside the library to pass extra params to the verification callback

function(accessToken, refreshToken, params, profile, cb) {
  console.log(params); // --> params contains all the data received during the accessTokenRequest
}

muralikg avatar Feb 14 '18 03:02 muralikg

@knightcode I followed your suggestions, but I got:

TypeError: Cannot read property 'message' of undefined
    at app.use (/app/server/src/main/setErrorHandler.ts:13:37)
    at Layer.handle_error (/app/node_modules/express/lib/router/layer.js:71:5)
    at trim_prefix (/app/node_modules/express/lib/router/index.js:315:13)
    at /app/node_modules/express/lib/router/index.js:284:7
    at Function.process_params (/app/node_modules/express/lib/router/index.js:335:12)
    at next (/app/node_modules/express/lib/router/index.js:275:10)
    at next (/app/node_modules/express/lib/router/route.js:127:14)
    at Layer.handle_error (/app/node_modules/express/lib/router/layer.js:67:12)
    at next (/app/node_modules/express/lib/router/route.js:135:13)
    at OAuth2Strategy.strategy.error (/app/node_modules/passport/lib/middleware/authenticate.js:356:9)
    at /app/node_modules/passport-oauth2/lib/strategy.js:169:36
    at _oauth2._request (/app/server/src/security/passport/passport.ts:102:20)
    at passBackControl (/app/node_modules/passport-oauth2/node_modules/oauth/lib/oauth2.js:132:9)
    at IncomingMessage.<anonymous> (/app/node_modules/passport-oauth2/node_modules/oauth/lib/oauth2.js:157:7)
    at IncomingMessage.emit (events.js:165:20)
    at endReadableNT (_stream_readable.js:1101:12)
    at process._tickCallback (internal/process/next_tick.js:152:19)

Have you seen this error before?

In this function, the access token is empty.

// https://github.com/jaredhanson/passport-oauth2/issues/73
oAuth2Strategy.userProfile = function (accesstoken, done) {
  console.log(accesstoken);
  // choose your own adventure, or use the Strategy's oauth client
  this._oauth2._request("GET", "https://login.microsoftonline.com/common/oauth2/v2.0/me/", null, null, accesstoken, (err, data) => {
    console.log(err);
    console.log(data);
    if (err) { return done(err); }
    try {
        data = JSON.parse( data );
    }
    catch(e) {
      return done(e);
    }
    done(null, data);
  });
};

Thanks!

junyuanz123 avatar Mar 01 '18 06:03 junyuanz123

@muralikg I still get an empty object, would you mind to share your code?

junyuanz123 avatar Mar 01 '18 06:03 junyuanz123

if(process.env.OAUTH2_CLIENT_ID){
	passport.use(new OAuth2Strategy({
		authorizationURL: process.env.OAUTH2_AUTHORIZE_URL,
		tokenURL: process.env.OAUTH2_TOKEN_URL,
		clientID: process.env.OAUTH2_CLIENT_ID,
		clientSecret: process.env.OAUTH2_CLIENT_SECRET,
		callbackURL: process.env.SITE_URL+"/auth/oauth2/callback",
	},
	function(accessToken, refreshToken, params, profile, cb) {
		User.findOrCreateOauth(params.info.email).then(function (user) {
		  return cb(null, user);
		}).catch(function (reason) {
			return cb(reason, null);
		});
	}));
}

@JunyuanZheng , I have tried this with digital ocean oauth2 and works fine

muralikg avatar Mar 01 '18 06:03 muralikg

@JunyuanZheng I got that very same error when mongoDB was not connecting correctly. I was at work and they had it blocked on the firewall which stumped me for a bit because I hadn't changed any code. You might double check that whatever DB you are using is not halting the oauth process

danschroeder avatar Mar 01 '18 07:03 danschroeder

const passport = require('passport')
// const { Strategy: GoogleStrategy } = require('passport-google-oauth20')
const { Strategy: GithubStrategy } = require('passport-github')
const { Strategy: OAuth2Strategy } = require('passport-oauth2')
const { GITHUB_CONFIG, OAUTH2_CONFIG} = require('../config')
const Profile = require('./profile')

module.exports = () => {
    // Allow passport to serialize and deserialize users into sessions
    passport.serializeUser((user, cb) => cb(null, user))
    passport.deserializeUser((obj, cb) => cb(null, obj))

    // The callback that is invoked when an OAuth provider sends back user
    // information. Normally, you would save the user to the database
    // in this callback and it would be customized for each provider
    const callback = (accessToken, refreshToken, params, profile, cb) => {
        console.log('access-token',accessToken)
        console.log('refresh-token',refreshToken)
        console.log('profile',profile)
        console.log('params',params)
        return cb(null, profile)
    }

    // Adding each OAuth provider's startegy to passport
    // passport.use(new GoogleStrategy(GOOGLE_CONFIG, callback))
    passport.use(new GithubStrategy(GITHUB_CONFIG, callback))
    const DjangoStrategy = new OAuth2Strategy(OAUTH2_CONFIG, callback)
    DjangoStrategy.userProfile = function(accessToken, done) {
        var self = this;
        this._userProfileURL = 'http://localhost:8001/accounts/profile/';
        this._oauth2.get(this._userProfileURL, accessToken, function (err, body, res) {
            var json;
            
            if (err) {
            if (err.data) {
                try {
                json = JSON.parse(err.data);
                } catch (_) {}
            }
            
            if (json && json.message) {
                return done(new APIError(json.message));
            }
            return done(new InternalOAuthError('Failed to fetch user profile', err));
            }
            
            try {
            json = JSON.parse(body);
            } catch (ex) {
            return done(new Error('Failed to parse user profile'));
            }

            console.log('json', json)
            
            var profile = Profile.parse(json);
            profile.provider  = 'oauth2';
            profile._raw = body;
            profile._json = json;
            done(null, profile);
        });
        }
    passport.use(DjangoStrategy)
}
    exports.parse = function(json) {
    if ('string' == typeof json) {
      json = JSON.parse(json);
    }
  
    var profile = {};
    profile.id = String(json.id);
    profile.displayName = json.name;
    profile.username = json.username;
    profile.email = json.email;
  
    return profile;
    };

or visit this link https://stackoverflow.com/a/58170646/8770790

faisallarai avatar Sep 30 '19 15:09 faisallarai

I was able to make it work by adding the scope array with other options.

let client = new OutlookStrategy({ authorizationURL: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', tokenURL: 'https://login.microsoftonline.com/common/oauth2/v2.0/token', clientID: configAuth.outlookAuth.clientID, clientSecret: configAuth.outlookAuth.clientSecret, callbackURL: configAuth.outlookAuth.redirectURL, scope: ['openid', 'profile', 'https://outlook.office.com/user.read'] }, function(accessToken, refreshToken, profile, cb) { // do stuff });

aaqilniz avatar Dec 04 '19 09:12 aaqilniz

So whether I write this with _oauth.get or _oauth._request it does not set the authorization header (or at least that's the error Aws Cognito returns to me:

client.userProfile = function (accessToken, done) {
  // axios worked
  // return axios
  //   .get("https://yttask.auth.us-east-1.amazoncognito.com/oauth2/userInfo", {
  //     headers: { Authorization: `Bearer ${accessToken}` }
  //   })
  //   .then((resp) => resp.data)
  //   .then((data) => done(null, data))
  //   .catch((err) => done(err));

  this._oauth2._request(
    "GET",
    "https://yttask.auth.us-east-1.amazoncognito.com/oauth2/userInfo",
    null,
    null,
    accessToken,
    (err, data) => {
      console.log(err);
      console.log(data);
      if (err) {
        return done(err);
      }
      try {
        data = JSON.parse(data);
      } catch (e) {
        return done(e);
      }
      done(null, data);
    }
  );
};

Am I doing something wrong? Using v1.6.1.

valexandersaulys avatar Feb 11 '22 01:02 valexandersaulys