Send user details to controller in passport authenticate using google-oauth2 - node.js

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 .

Related

req.user not available in Google Passport Strategy

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.)

Express Passport Google Strategy - using res.redirect

I have an express app which manages authentication via Passport with support for both local and Google strategies.
At the moment I support account creation via Google auth but actually a user's Google account doesn't contain all the info that my application requires. Because of this I would like to redirect uses who don't have a local account to my own account creation and only support account linking (i.e. their local account can be linked to Google account for authorization).
The problem is that I would normally do this via res.redirect but res is not available in the Google Strategy - what is the best way to handle this?
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",
passReqToCallback: true
},
// google will send back the token and profile
function(req, accessToken, refreshToken, profile, done) {
// asynchronous
process.nextTick(function() {
// check if the user is already logged in
if (!req.user) {
// 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 { <--here is where I want to redirect
//currently I create a new user but i really want to redirect them to create a local account
// 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;
Any help much appreciated!

How to handle, check if user is logged in with Passport Slack Strategy and Passport-local

Hello I am needing some assistance with setting up a function to check if a user is logged in or not with Passport.js using the slack strategy.
I have a localstrategy which works fine but I also want to implement a sign in with slack as well.
I have added my app to slack and set the redirect URL. Everything is working fine however regardless if a user signs in or not they can still access the main page from the URL.
Example I have my login page 123.456.7.891:3000/login and when a user logs in through the local login or slack it redirects to 123.456.891:3000/index. Well if you know the URL for index you can just navigate to it.
For the local strategy I used this function to prevent that. It checks if the user is logged in or not.
function isLoggedIn(req, res, next){
if(req.isAuthenticated()){
return next();
}
req.flash("error", "Must be signed in first to access this page.");
res.redirect("/login");
}
And than I simply add the isLoggedIn function to the route like this.
app.get("/QAApplicationHub", isLoggedIn, (req, res) => {
Application.find({}, (err, allApplications) => {
if(err){
console.log(err);
} else {
res.render("index", { application: allApplications });
// username: req.user.name
}
});
});
The issue I am having with when a user logs in with slack is when they are redirected to the redirect URL it just takes them back to the login page stating that the user must be logged in to access the page. The message appears because I have flash set up to show the user the error. It seems that with my current code the isLoggedIn only checks for the local login and not slack.
So how can I implement the isLoggedIn function for both the local and slack strategy? Or what method is it that I need to implement for it to work for both.
This is my code for Passport-slack.
// Configure the Slack Strategy
passport.use(new SlackStrategy({
clientID: process.env.SLACK_CLIENT_ID = '123456',
clientSecret: process.env.SLACK_CLIENT_SECRET ='123abc',
}, (accessToken, scopes, team, extra, profiles, done) => {
done(null, profiles.user);
}));
//=============================
//LOCAL LOGIN MIDDLEWARE
//=============================
app.post("/login", passport.authenticate("local", {
successRedirect: "/QAApplicationHub",
failureRedirect: "/login",
failureFlash: true
}));
app.get("/logout", (req, res) => {
req.logout();
req.flash("success", "Successfuly signed out!")
res.redirect("/login");
});
// =============================
// PASSPORT-SLACK MIDDLEWARE
// =============================
// path to start the OAuth flow
app.post('/auth/slack', passport.authorize('slack', {
successRedirect: "/QAApplicationHub",
failureRedirect: "/login",
failureFlash: true
}));
// OAuth callback url
app.get('/auth/slack/callback',
passport.authorize('slack',
(req, res) => {
res.redirect('/QAApplicationHub')
}));
I believe users are stored in the req.user property. Try this:
function isLoggedIn(req, res, next){
if(!req.user){
req.flash("error", "Must be signed in first to access this page.");
res.redirect("/login");
} else {
return next();
}
}
So turns out I was missing quit a bit of code to make this complete. First I needed to add the user schema for slack. Second needed to add a function to create the user and save it to my db and a function to check if a user had already signed in with slack and to just compare the ID instead of making a new instance of the user to the DB. Also needed to update the serialize user and de-serialize user.
Here's my schema
let mongoose = require("mongoose"),
passportLocalMongoose = require("passport-local-mongoose");
let UserSchema = new mongoose.Schema({
local: {
name: String,
username: String,
password: String
},
slack: {
username: String,
slackid: Number,
name: String
}
});
UserSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("User", UserSchema);
And than the set up for the passport strategies.
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id).then((user) => {
done(null, user.id);
});
});
passport.use(new LocalStrategy(User.authenticate()));
// Configure the Slack Strategy
passport.use(
new SlackStrategy({
callbackURL: "/auth/slack/redirect",
clientID: keys.slack.clientID,
clientSecret: keys.slack.clientSecret
}, (accessToken, refreshToken, profile, done) => {
console.log(profile);
// Check if user already exist in DB
User.findOne({username: profile.displayName}).then((currentUser) => {
if(currentUser){
// already have the user
console.log('User is', currentUser);
done(null, currentUser);
} else {
// If not, create new user in DB
new User({
username: profile.displayName,
slackid: profile.id
}).save().then((newUser) => {
console.log("new user created: " + newUser);
done(null, newUser);
});
}
});
}));
Also needed to update the routes for the auth and call back.
app.post('/auth/slack',
passport.authenticate('Slack', {
scope:['identity.basic'],
}));
// OAuth callback url
app.get('/auth/slack/redirect', passport.authenticate('Slack'), (req, res) => {
res.redirect("/QAApplicationHub");
});
Hopes this helps others in need of passport.js help. For details info I recommend looking up The Net Ninja on YT. He helped me a lot with my code gap and helped understand what was actually going on.

Google oauth not returning email passport authentication

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"

Connect Local Users to Facebook/Twitter with PassortJS

I have an existing user base, that I want to allow them to link with their Facebook/Twitter Accounts. Looks like I can use passportjs but am having difficulty,
Right now, a url pass in their user/pass, I do a local stragety, and look up the user. Then I want to perform an authorize I presume. I get the facebook promopts asking if I want to allow, and I hit the callback with the accessToken, but I have no way to tie that token to an existing user in my db as I see now way to get the request info.
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
Users.findOne({_id: user._id}, function(err, user) {
return done(err, user);
});
});
passport.use(new LocalStrategy({ passReqToCallback: true }, function(req, username, password, done) {
Users.findOne({_id: username}, function(err, user) {
return done(null, user);
});
}
));
passport.use(new FacebookStrategy({
clientID: 'xxxx',
clientSecret: 'xxxx',
callbackURL: "http://xxxx/facebook/callback",
passReqToCallback: true
},
function(req, accessToken, refreshToken, profile, done) {
// How would I get access to the user found in the LocalStrategy here so I can associate the accessToken to the user in the db?
console.log(accessToken);
console.log(refreshToken);
var err = null;
var user = {};
return done(err, user);
}
));
server.get('/facebook', passport.authenticate('local', { failureRedirect: '/login', failureFlash: true }),
function (req, res) {
if (req && req.user) {
passport.authorize('facebook', { scope: ['publish_actions','manage_pages'] })(req, res);
}
});
server.get('/facebook/callback',
passport.authorize('facebook', { failureRedirect: '/facebook/failure', successRedirect: '/facebook/success' }),
function(req, res) {
res.redirect('/');
}
);
Assuming that the user is already logged in with the local strategy, the user should be available on the request as req.user. You can then add the facebook credential information to the existing user object.
Check to see which URL you are redirecting to on your Facebook callback and make sure that you aren't generating different sessions between the time that you log in locally and the time that you are directed to your callback from Facebook.
I had a similar issue that was driving me crazy, and it's because my redirects were different.
For example, I was navigating to http://localhost:3000 in Chrome, logging in and req.user was being set correctly. On my Facebook dev settings, and Passport config, I was redirecting to http://hostname.local:3000/auth/facebook.
As long as your local login and redirect share the same session, then req.user should be available.

Resources