I am using passport-facebook to handle facebook login for my website. Here is the relevant code:
Less Important:
passport.use(new FacebookStrategy({
// pull in our app id and secret from our auth.js file
clientID: configAuth.facebookAuth.clientID,
clientSecret: configAuth.facebookAuth.clientSecret,
callbackURL: configAuth.facebookAuth.callbackURL
},
// facebook will send back the token and profile
function (token, refreshToken, profile, done) {
// asynchronous
process.nextTick(function () {
//check for the user in db
...
if(user found..){
return done(null, user); // user found, return that user
}
else{
// if there is no user found with that email id, create them
var newUser = {};
............
return done(null, newUser);
}
Serialise Deserialise:
// used to serialize the user for the session
passport.serializeUser(function (user, done) {
console.log('serializing user.');
done(null, user.id);
});
// used to deserialize the user
passport.deserializeUser(function (id, done) {
getDBHandle(function (db) {
db.collection('users').findOne({'id':id}, function (err, user) {
console.log('deserialize user.',id,user);
done(err, user);
});
});
});
Caller:
// route for facebook authentication and login
app.get('/auth/facebook', passport.authenticate('facebook', { scope : 'email' }));
// handle the callback after facebook has authenticated the user
app.get('/auth/facebook/callback',
passport.authenticate('facebook', {
successRedirect : '/profile',
failureRedirect : '/'
}));
Imporant:
Once the user is logged in, then the loggedin user info is saved in req.user as per passport.
app.get('/isAuthenticated',function(req,res){
...
res.send(req.user);
});
Now lets say, user logs out from her facebook account, how would passport knows that logout event has occured.
Essentially, if a user logs out from fb, then even our app should consider the user to be not logged in.
I tried calling isAuthenticated after I logout my fb, passport still returns same user information.
How should I handle this situation?
Do I need to call /auth/facebook everytime to check whether user is authenticated, instead of depending upon req.user?
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.)
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.
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 have an issue with Facebook Strategy failing after successfully logging into Facebook. I'm using Passport Local and Passport Facebook, but independent of each other, here are the code what I have shared.
passport.use(new FacebookStrategy({
clientID: 'XYZId',
clientSecret: 'XYZSecret',
callbackURL: "/auth/facebook/callback"
},
function(accessToken, refreshToken, profile, done) {
console.log(profile);
userDetails = profile;
return done();
}
));
app.get('/auth/facebook', passport.authenticate('facebook'));
app.get('/auth/facebook/callback',
passport.authenticate('facebook', {
failureRedirect: '/login' }),
function(req, res) {
console.log("req");
console.log(userDetails);
console.log("End of Req");
res.redirect('/');
});
Is there anything wrong in this code? Also, for local strategy I have modified a bit which is working perfectly fine.
// config/passport.js
// load all the things we need
var LocalStrategy = require('passport-local').Strategy;
// load up the user model
var mysql = require('mysql');
var bcrypt = require('bcrypt-nodejs');
var dbconfig = require('./database');
var connection = mysql.createConnection(dbconfig.connection);
connection.query('USE ' + dbconfig.database);
// expose this function to our app using module.exports
module.exports = function(passport) {
// =========================================================================
// passport session setup ==================================================
// =========================================================================
// required for persistent login sessions
// passport needs ability to serialize and unserialize users out of session
// used to serialize the user for the session
passport.serializeUser(function(user, done) {
done(null, user.id);
});
// used to deserialize the user
passport.deserializeUser(function(id, done) {
connection.query("SELECT * FROM users WHERE id = ? ",[id], function(err, rows){
done(err, rows[0]);
});
});
// =========================================================================
// LOCAL SIGNUP ============================================================
// =========================================================================
// we are using named strategies since we have one for login and one for signup
// by default, if there was no name, it would just be called 'local'
passport.use(
'local-signup',
new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'username',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, username, password, done) {
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
connection.query("SELECT * FROM users WHERE username = ?",[username], function(err, rows) {
if (err)
return done(err);
if (rows.length) {
return done(null, false, req.flash('signupMessage', 'That username is already taken.'));
} else {
// if there is no user with that username
// create the user
console.log(req.body);
var newUserMysql = {
uname: req.body.uname,
username: username,
userphone: req.body.userphone,
password: bcrypt.hashSync(password, null, null) // use the generateHash function in our user model
};
var insertQuery = "INSERT INTO users ( uname, username, password, userphone ) values (?,?,?,?)";
console.log(insertQuery);
connection.query(insertQuery,[newUserMysql.uname, newUserMysql.username, newUserMysql.password, newUserMysql.userphone],function(err, rows) {
newUserMysql.id = rows.insertId;
return done(null, newUserMysql);
});
}
});
})
);
// =========================================================================
// LOCAL LOGIN =============================================================
// =========================================================================
// we are using named strategies since we have one for login and one for signup
// by default, if there was no name, it would just be called 'local'
passport.use(
'local-login',
new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'username',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, username, password, done) { // callback with email and password from our form
connection.query("SELECT * FROM users WHERE username = ?",[username], function(err, rows){
if (err)
return done(err);
if (!rows.length) {
return done(null, false, req.flash('loginMessage', 'No user found.')); // req.flash is the way to set flashdata using connect-flash
}
// if the user is found but the password is wrong
if (!bcrypt.compareSync(password, rows[0].password))
return done(null, false, req.flash('loginMessage', 'Oops! Wrong password.')); // create the loginMessage and save it to session as flashdata
// all is well, return successful user
return done(null, rows[0]);
});
})
);
};
Console Log :
You didn't store authorized facebook user in session. You just call function done() without parameters in the implementation of FacebookStrategy. First, you should store fb user in your database or select if exist then call function done (receives first param as error, second as user object). here's docs
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.