jaredhanson / passport-oauth2

OAuth 2.0 authentication strategy for Passport and Node.js.
https://www.passportjs.org/packages/passport-oauth2/?utm_source=github&utm_medium=referral&utm_campaign=passport-oauth2&utm_content=about
MIT License
607 stars 343 forks source link

Profile info of user empty #73

Open codeeverystory opened 7 years ago

codeeverystory commented 7 years ago

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'); });

knightcode commented 7 years ago

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);
danschroeder commented 6 years ago

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

muralikg commented 6 years ago

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
}
junyuanz123 commented 6 years ago

@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 commented 6 years ago

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

muralikg commented 6 years ago
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

danschroeder commented 6 years ago

@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

faisallarai commented 5 years ago
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

aaqilniz commented 4 years ago

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


    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
});```
valexandersaulys commented 2 years ago

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.

noeltimothy commented 2 months ago

If your params returns an id_token, you could just use jwt to decode it. Try this:

const oktaStrategy = new Strategy({
        authorizationURL: '{url}/oauth2/default/v1/authorize',
        tokenURL: '{url}/oauth2/default/v1/token',
        clientID: "xxxxxxxxxxxxx",
        clientSecret: "XXXXXXX",
        callbackURL: '{myurl}/callback',
}, (accessToken, refershToken, params, profile, done) => {
        const decoded = jwtDecode(params['id_token']);
        done(null, decoded);
});
RichardJECooke commented 1 month ago

I'll make a pull request to add params to the README. Wasted so much time trying to figure out where the user's email was