I am running into a 500 Internal Server server error when my callback route is called for a Google login and I'm not sure what part of my code is causing the issue. As you can see from my terminal results, it is passing the if statement condition and triggering the cb function which is responding and outputting [object SequelizeInstance:external_account]. Could the output be the incorrect value? I'm not exactly sure what value should be getting accepted by the call.
Console Log:
If statement was true externalId and Email present
GET /auth/google/callback?code=4/mvFTIURH5P0ACQFwZobC04-ftehalfdf4454 500 434.737 ms - 44
[object SequelizeInstance:external_account]
PassportJS setup:
/*==== Google Configuration ====*/
passport.use(new GoogleStrategy({
clientID: 'client-id',
clientSecret: 'client-secret',
callbackURL: 'http://localhost:3000/auth/google/callback'
}, function(accessToken, refreshToken, profile, cb) {
var user;
models.User.findOne({
where: {
email: profile.emails[0].value
}
}).then(function(_user){
user = _user;
return models.ExternalAccount.findOne({
where: {
externalSourceId: profile.id
}
}).then(function(externalAccount, err){
if(user && externalAccount){
console.log("If statement was true externalId and Email present");
return cb(externalAccount, err)
} else if (user){
console.log("Else If statement was true Email was present, but no account Id");
return models.ExternalAccount.create({
externalSource: "Google",
externalSourceId: externalAccount.externalSourceId,
userId: externalAccount.user.id
}).then(function(){
return cb(externalAccount, err)
});
} else {
console.log('Error');
}
})
})
}));
routes:
/*==== /AUTH/GOOGLE ====*/
siteRoutes.route('/auth/google')
.get(passport.authenticate('google', { scope: ['profile', 'email'] }));
/*==== /AUTH/GOOGLE/CALLBACK ====*/
siteRoutes.route('/auth/google/callback')
.get(passport.authenticate('google', {
successRedirect : '/app',
failureRedirect : '/login',
failureFlash: 'Invalid Google credentials.'
}),function(req, res){
res.redirect('/app');
});
Error might have been occurred from google's server if you're not using https.
Google login api only accepts the secured network using ssl.
Try with ssl. if you're still on this problem...
Related
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!
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.)
I have created a google user session using passport oauth, using the following routes:
nodeApp.get('/api/google', googlepassport.authenticate('google', { session: true,scope: ['profile', 'email'] })
nodeApp.get('/auth/google/callback',
googlepassport.authenticate('google',
{ // successRedirect: '/',
// failureRedirect: '/login-error',
}))
At the backend in passport.serializeuser, I am able to retrieve details however I want to send these details to client controller.
I tried using Http.get, but that doesn't work for google oauth as I get cross domain error. I am new to nodejs, so kindly suggest best possible way to do send user details to client controller.
google strategy
googlepassport.use(new GoogleStrategy({
clientID: "add",
clientSecret: "",
callbackURL: "---",
// passReqToCallback: true
},
function(req, token, refreshToken, profile, done) {
// make the code asynchronous
// console.log("test google");
// User.findOne won't fire until we have all our data back from Google
process.nextTick(function() {
console.log("test google");
//console.log(profile);
var userMap = {};
userMap['mail'] = profile.emails[0].value;
userMap['name'] = profile.displayName;
// console.log(JSON.stringify(userMap));
// req.user = req.session.googlepassport.user;
//console.log(req.session);
//req.session.user = req.session.googlepassport.user;
return done(null, userMap);
})
// done(null,null);
}));
googlepassport.serializeUser(function(user, done) {
console.log('Serializing');
console.log(user);
done(null, user);
});
googlepassport.deserializeUser(function(userMap, done) {
done(null, userMap);
});
I want to send userMap to the client, Here the route is made through href, to avoid cross-domain error, that I get if I use it on button click .
I am trying to make a sign in with google button using passport module of node js. I am trying to get person's email id, name, profile pic. I am trying to download pic to local server. Google is not returning email id even after adding 'email' to scope and nor the returned link for profile pic is working. I have looked into various answers to this question but all say to include userinfo.email. It has been deprecated now. As per google documentation new scope parameter is email.
Below is my code. Any help is appreciated.
Passport
passport.use(new GoogleStrategy({
clientID : configAuth.googleAuth.clientID,
clientSecret : configAuth.googleAuth.clientSecret,
callbackURL : configAuth.googleAuth.callbackURL,
},
function(token, refreshToken, profile, done) {
// make the code asynchronous
// User.findOne won't fire until we have all our data back from Google
process.nextTick(function() {
// try to find the user based on their google id
User.findOne({ 'google.id' : profile.id }, function(err, user) {
if (err)
return done(err);
if (user) {
// if a user is found, log them in
return done(null, user);
} else {
// if the user isnt in our database, create a new user
var newUser = new User();
console.log(profile);
//JSON.parse(profile);
// set all of the relevant information
newUser.google.id = profile.id;
newUser.google.token = profile.token;
newUser.google.name = profile.displayName;
newUser.google.uname = profile.emails[0].value; // pull the first email
newUser.google.dp = profile._json.picture;
console.log('url is');
console.log(newUser.google.name);
console.log(newUser.google.dp);
//console.log(profile.picture);
Download(newUser.google.uname, newUser.google.dp,function(err){
if(err)
console.log('error in dp');
else
console.log('Profile Picture downloaded');
});
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
});
}));
};
routes.js
app.get('/connect/google', passport.authorize('google', { scope : ['profile', 'email'] }));
// the callback after google has authorized the user
app.get('/connect/google/callback',
passport.authorize('google', {
successRedirect : '/profile',
failureRedirect : '/'
}));
download.js
module.exports = function(username, uri, callback){
var destination;
request(uri).pipe(fs.createWriteStream("./downloads/"+username+".png"))
.on('close', function(){
console.log("saving process is done!");
});
I had the same problem and wrote the scope in this way:
app.get('/connect/google', passport.authenticate('google', {
scope: [
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/userinfo.email'
]
}));
And you will get the email:
function(accessToken, refreshToken, profile, done) {
console.log(profile.emails[0].value);
});
I hope this helps you.
The above answer definitely works, there is also one more way to approach this.
app.get('/auth/google',
passport.authenticate('google', { scope: ['profile', 'email'] })
);
In your routes.js with the profile add email.
This should resolve your issue.
According to google documentation for oauth, the first parameter has to be openid and the second can be email or profile, or both
app.get('/auth/google',
passport.authenticate('google', {scope: ['openid', 'email', 'profile']})
);
documentation
Add the line of code after callbackUrl.
userProfileURL: "https://www.googleapis.com/oauth2/v3/userinfo"
I'm incorporating Facebook authentication in my app using Passport with Node and Express, and MySQL db (using Bookshelf.js ORM). When I go to log in using Facebook, after entering my Facebook credentials, I get the error 'cannot get /auth/facebook/callback?code= {callback_code}'. I've tried the following solutions I found on stackoverflow: 1, 2, 3, 4
And this outside post: 5
None of these have worked.
In my FB app account, I've set 'Valid OAuth redirect URIs' inside of Client OAuth Settings to 'localhost:8080/auth/facebook/callback', App Domain to 'localhost', and Site URL to 'localhost:8080/'.
Below is my code. Any help is much appreciated!
Routes:
module.exports = function (apiRouter, passport) {
apiRouter.get('/events', isLoggedIn, eventController.getAllEvents);
apiRouter.post('/events', eventController.addEvent);
apiRouter.get('/auth/facebook', passport.authenticate('facebook', {scope : ['email ', 'public_profile', 'user_friends']}));
apiRouter.get('/auth/facebook/callback',
passport.authenticate('facebook', {failureRedirect: '/'}))
};
passport config:
passport.use(new FacebookStrategy({
clientID : configAuth.facebookAuth.clientID,
clientSecret : configAuth.facebookAuth.clientSecret,
callbackURL : 'http://localhost:8080/auth/facebook/callback'
},
function(token, refreshToken, profile, done) {
console.log('looking for user from fb');
process.nextTick(function() {
console.log('looking for user from fb inside async');
new User({ 'facebook_id' : profile.id })
.fetch()
.then(function(userModel) {
if (userModel) {
return userModel;
} else {
new User({
facebook_token: token,
first_name: profile.name.givenName,
last_name: profile.name.familyName
}).save()
.then(function(model) {
console.log('New user saved', model);
done(null, model);
},function(error) {
console.log('Error saving new user: ', error);
done(error);
});
}
done(null, userModel);
}, function(error) {
console.log('Error: ', error);
done(error);
});
});
}));
How do I get the message and display it in my router.post('/auth')?
passport.use(new FacebookTokenStrategy({
clientID: 'HIDDEN',
clientSecret: 'HIDDEN'
}, function(accessToken, refreshToken, profile, done) {
console.log(profile);
var user = {id: profile.id, first_name: profile.name[1], last_name: profile.name[0], email: profile.emails[0], profile_picture: profile.photos[0]};
var error = null;
return done(error, user, {message: "HOW TO RETRIEVE THIS MESSAGE!"});
}
));
I've tried to retrieve this message by saying console.log(req.message) or console.log(req.session.message), I just don't know how to get it. I've also tried console.log(req.session.passport.message)
router.post('/auth', passport.authenticate('facebook-token'), function(req, res){
console.log("Verifying");
console.log("HOW TO LOG THAT MESSAGE HERE?");
if(req.isAuthenticated()){
console.log(req.session.passport.user);
}else{
console.log("NOT");
}
});
I don't think that the third argument is passed in any way if the authentication was successful (which in your case it always is; by default, Passport will return a 401 when authentication was unsuccessful, and your handler wouldn't get called at all).
However, you can add extra properties to req if you configure the strategy to pass it to the verification callback:
passport.use(new FacebookTokenStrategy({
clientID: 'HIDDEN',
clientSecret: 'HIDDEN',
passReqToCallback : true,
}, function(req, accessToken, refreshToken, profile, done) {
req.message = 'Your Message Here';
...
}
}))
And then in your handler, you can access it as req.message as well.