-->

req.user not available in Google Passport Strategy

2019-09-01 05:31发布

问题:

I have an express app which manages authentication via Passport, initially with a local strategy. To this I have just added Google sign in / account creation and almost everything works as per the docs.

The problem I have is that a user can create an account using the Google Strategy but I cannot quite get it so that an authenticated user (via the local strategy) can simply add additional Google details to their account so that they can use either the local or Google strategy.

In 'index.js' where I define my routes I define const passportGoogle = require('../handlers/google'); which has the details of my Google Strategy.

Further down in index.js I have my authenticate and authorise routes:

/* GOOGLE ROUTES FOR AUTHENTICATION*/

router.get('/google',
    passportGoogle.authenticate('google', 
    { scope: ['profile', 'email'] }));

router.get('/google/callback', 
    passportGoogle.authenticate('google', 
    {
        failureRedirect: '/',
        failureFlash: 'Failed Login!',
        successRedirect: '/account',
        successFlash: 'You are logged in!'

    }
));

/* GOOGLE ROUTES FOR AUTHORISATION - IE A USER IS ALREADY LOGGED IN AND WANTS TO CONNECT THEIR GOOGLE ACCOUNT*/

// send to google to do the authentication
router.get('/connect/google', 
    passportGoogle.authorize('google', 
    { scope : ['profile', 'email'] }
));

// the callback after google has authorized the user
router.get('/connect/google/callback',
    passportGoogle.authorize('google', {
        successRedirect : '/profile',
        failureRedirect : '/'
    })
);

As above my Google strategy is defined in google.js:

var passport = require('passport');
var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
var User = require('../models/User');
passport.use(new GoogleStrategy({
    clientID: process.env.GOOGLE_CLIENTID,
    clientSecret: process.env.GOOGLE_CLIENTSECRET,
    callbackURL: "http://127.0.0.1:7777/google/callback"
  },
  // google will send back the token and profile
function(req, token, refreshToken, profile, done) {
// console.log('here is res.locals.user'+ res.locals.user);
        console.log('here is req.user'+ req.user);
    // asynchronous
    process.nextTick(function() {

        // check if the user is already logged in
        if (!req.user) {
            console.log('THERE IS NO REQ.USR');
            // find the user in the database based on their facebook id
            User.findOne({ 'google.id': profile.id }, function(err, user) {

                // if there is an error, stop everything and return that
                // ie an error connecting to the database
                if (err)
                    return done(err);

                // if the user is found, then log them in
                if (user) {
                    return done(null, user); // user found, return that user
                } else {
                    // if there is no user found with that google id, create them
                    var newUser            = new User();

                    // set all of the facebook information in our user model
                    newUser.google.id = profile.id;
                    newUser.google.token = token;
                    newUser.name = profile.displayName;
                    newUser.email = profile.emails[0].value;

                    // save our user to the database
                    newUser.save(function(err) {
                        if (err)
                            throw err;

                        // if successful, return the new user
                        return done(null, newUser);
                    });
                }

            });

        } else {
            const user = User.findOneAndUpdate(
                { _id: req.user._id },
                { $set: {"user.google.id":profile.id, 
                        "user.google.token":accessToken, 
                        "user.google.name":profile.displayName, 
                        "user.google.email":profile.emails[0].value
                    }
                },   
                { new: true, runValidators: true, context: 'query' }
            )
            .exec();
            return done(null, user);
            req.flash('success', 'Google details have been added to your account'); 
            res.redirect(`back`); 
        }
    });
}));

module.exports = passport;

However when a user is signed in and follows the link to /connect/google a new user is always created rather than their details updated. My logging shows that if (!req.user) condition in the Google stratgy is always firing but I'm not sure why that is since the user is definitely logged in.

Any help much appreciated!

回答1:

In order to access the req in your callback, you need a passReqToCallback: true flag in your GoogleStrategy config object:

passport.use(new GoogleStrategy({
    clientID: process.env.GOOGLE_CLIENTID,
    clientSecret: process.env.GOOGLE_CLIENTSECRET,
    callbackURL: "http://127.0.0.1:7777/google/callback",
    passReqToCallback: true
  },
  // google will send back the token and profile
function(req, token, refreshToken, profile, done) {
// console.log('here is res.locals.user'+ res.locals.user);
        console.log('here is req.user'+ req.user);
....
})

If this flag is omitted, the expected callback form is

function(accessToken, refreshToken, profile, done){...}

So your code is looking for a user property on the accessToken that Google sends back, which should always fail. I also bring this up because, if I'm right, other parts of your function should also be misbehaving. (Like User.findOne({'google.id': profile.id}) should always fail, because the function is called with done as its fourth argument rather than profile.)