How would I redirect after authenticating a user using passportjs? - node.js

I looked in several posts, but I cannot find something that meets my situation.
To login (or signup) with google, you have to go to mydomain.com/login/google
You then, well, login with google, and then the callback is handled on mydomain.com/auth/google
Here is the code responsible for this.
app.get('/login/google', passport.authenticate('google'));
app.get('/auth/google',
passport.authenticate('google', { failureRedirect: '/login', failureMessage: false, session: false, failureFlash: true }),
function(req, res) {
res.redirect('/');
});
Here is where I store the users:
passport.use(new GoogleStrategy({
clientID: no,
clientSecret: definitely not,
callbackURL: 'https://no cuz privacy/auth/google'
},
async function(issuer, profile, cb) {
var user = await User.find({ google: true, googleid: profile.id })
if (!user[0]) {
// The Google account has not logged in to this app before. Create a
// new user record and link it to the Google account.
const newUser = await new User({ username: generateUsername(), google: true, googleid: profile.id, googleProfile: profile })
await newUser.save()
return cb(null, newUser);
} else {
// The Google account has previously logged in to the app. Get the
// user record linked to the Google account and log the user in.
console.log('exists')
return cb(null, newUser);
}
}
));
I think you have to do something with the callback function (cb()) to somehow go to app.get('/auth/google') for the redirect, but, all it does is print either exists or new in the console, spinning forever on the client side. Not sure how to redirect after the code determines either account exists or new account.
Edit: I just want to point out that the cb() function could also be done() too. For example:
function(accessToken, refreshToken, profile, done){
console.log("strategy");
console.log(profile);
console.log(accessToken);
console.log(refreshToken);
done(null, profile);
}
^^ Not my code --> PassportJS in Nodejs never call the callback function ^^

Related

How to Get user Email Address using OAuth in Node js

I am writing a code to add functionality of logging in with Google. I have written code but when a user log in with google, it only gives me id, name, fullname etc. It does not provide with user email address. Can any one help me to solve this? Following is my code
passport.use(new GoogleStrategy({
clientID: CLIENT_ID,
clientSecret: CLIENT_SECRET,
callbackURL: "http://localhost:8000/auth/google/notepad"
},
function(accessToken, refreshToken, profile, cb) {
console.log(profile);
User.findOrCreate({ googleId: profile.id }, function (err, user) {
return cb(err, user);
});
}
));
router.get('/auth/google', passport.authenticate('google',{scope: ['profile']}));
router.get('/auth/google/notepad',
passport.authenticate('google', { failureRedirect: '/' }),
async function(req, res) {
const token = await req.user.generateAuthToken();
res.cookie('authToken', token);
res.redirect('/')
});
You are missing the email scope. It’s a separate scope to their profile.
See the docs too if you want to know more: https://developers.google.com/identity/protocols/oauth2/openid-connect#scope-param

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

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.

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

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 .

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