Passport-facebook : 'cannot get /auth/facebook/callback?code= {callback_code} - node.js

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);
});
});
}));

Related

Passportjs Google OAuth does not return profile object

I am working on a MERN stack appliaction and I wanna integrate google authencation using PassportJs, I already configured the server-side and ran a couple of test which were successful but all of a sudden it no longers returns the usual profile object which contains the user's name, first_name, _json e.t.c, instead it sends back this object;
{
access_token: 'ya29.A0ARrdaM8QpjEP_3sDyDRBT8OJiOlXVgOHFcyEV1nne13jd_qRelaTYh5ry0H0E8WvmOs14h6PycgTHqteS85U9lPxj2sfhnabOI6XdMgWAM_Z_y4WR1F50NR7MVDcjpH6aS8xLzewScSt8R-6Cs6t4Adn3Vgu',
expires_in: 3599,
scope: 'https://www.googleapis.com/auth/userinfo.email openid https://www.googleapis.com/auth/userinfo.profile',
token_type: 'Bearer',
id_token: 'eyJhbGciOiJSUzI1NiIsImtpZCI6ImNlYzEzZGViZjRiOTY0Nzk2ODM3MzYyMDUwODI0NjZjMTQ3OTdiZDAiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI5NDc2NDU0NDY5NTctZWhiNXN1dWduZnZkcGJzNnE5cXZjNDQ2ZWRzZTY4ZWwuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI5NDc2NDU0NDY5NTctZWhiNXN1dWduZnZkcGJzNnE5cXZjNDQ2ZWRzZTY4ZWwuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMDA3NDM5NTc3MzIwMDkwMzE5OTAiLCJlbWFpbCI6InNpbW9uY2NvdmVuYW50QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdF9oYXNoIjoiODBfN20xMVJVNHV5SHZiblc2UEZIZyIsIm5hbWUiOiJTaW1vbiBDb3ZlbmFudCIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQVRYQUp4QWtNdEhtSWFjLV9CNEFzVkVYQU5jMUF1WEh5QU5KNTdFVVRVPXM5Ni1jIiwiZ2l2ZW5fbmFtZSI6IlNpbW9uIiwiZmFtaWx5X25hbWUiOiJDb3ZlbmFudCIsImxvY2FsZSI6ImVuIiwiaWF0IjoxNjQ5MTA5MDk5LCJleHAiOjE2NDkxMTI2OTl9.cRn6YA8OYSkp_MGLoDQTqeu6R5Ajm3KQMg6cQkZaBXuduJIJOnVL1r-lgCAIDHmkTsH-gohBIWcELPL0Pzm0rueW2X6b0LEzPdFNMsHFL_RkbRh2bwPwqAZqToaEJsN6M9DqqQwjuc8aENwHOxflVfTqM71aidt96cEIucRcCYxF1Q-rxBw4STNy0c2Lqae_85fFO5uArEJPyOPYDjVYjEqR0wNWFezRadA8zAKV7tv2WJFhEbA2tgnnbIKP5rWmkF6V6mlbFKv9p2qFvBLUpj6ffqVnQZmwILJng6GvNrWu03VfbAvHao4PA-qLwPnge65hqjet3S8TxzlNkkAtDA'
}
This my passport setup:
const strategy = new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "/auth/google/callback",
},
function (accessToken, refreshToken, profile, cb, done) {
console.log(profile);
const { email, given_name, family_name, email_verified, gender } = profile._json;
User.findOne({ email }).exec((err, user) => {
if (user) {
const token = jwt.sign({ _id: user._id }, process.env.JWT_SECRET, {
expiresIn: "7d",
});
const { password, ...others } = user._doc;
return cb(err, others, token);
} else {
let password = email + process.env.JWT_SECRET;
user = new User({
firstname: given_name,
lastname: family_name,
email,
password: CryptoJS.AES.encrypt(
password,
process.env.PASS_SEC
).toString(),
gender: "male",
});
console.log(user);
user.save((err, data) => {
console.log(data);
if (err) {
console.log("Save error", errorHandler(err));
return done(null, false, { message: errorHandler(err) });
} else {
const token = jwt.sign({ _id: data._id }, process.env.JWT_SECRET, {
expiresIn: "7d",
});
const { password, ...others } = data._doc;
console.log(others);
return cb(err, token, others);
}
});
}
});
}
);
passport.use(strategy);
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
And this is my routes :
router.get(
"/google",
passport.authenticate("google", {
scope: ["profile", "email"],
})
);
router.get(
"/google/callback",
passport.authenticate("google", {
successRedirect: "/login/success",
failureRedirect: "/login/failed",
})
);
I thought maybe it had something to do with the accessToken so I tried using the "passport-oauth2-refresh" dependency to refresh and get a new accessToken;
const refresh = require("passport-oauth2-refresh");
refresh.use(strategy);
But that didnt work, I have tried searching stack-overflow for similar issues but found none. The strange thing was that it was working before, cant seem to figure out what went wrong.
I also wanna ask, after successful authentication I want to be able to send the user to my frontend and store it in localStorage but I found out that Google or Oauth don't support CORS. So I cannot make axios request nor catch server response, so what would be the optimal way to handle the authentication on the frontend?
My frontend google login code:
const google = () => {
window.open(`${process.env.REACT_APP_API_URL}/auth/google`, "_self");
}

Google OAuth Nodejs handle error using passport, passport-google-oauth20

I'm writing authentication Nodejs API using passport, passport-google-oauth20
Everything is work but the problem is now I want to verify email of the user via domain. My system just allows email with domain #framgia.com can log in to. If not, send the user back a message.
My code here:
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const mongoose = require('mongoose');
const keys = require('../config/keys');
const User = mongoose.model('users');
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id).then(user => {
done(null, user);
})
});
passport.use(
new GoogleStrategy(
{
clientID: keys.googleClientID,
clientSecret: keys.googleClientSecret,
callbackURL: '/auth/google/callback',
},
async (accessToken, refreshToken, profile, done) => {
const existingUser = await User.findOne({ googleId: profile.id });
if (existingUser) {
return done(null, existingUser);
}
if (!profile._json.domain || profile._json.domain !== 'framgia.com') {
return done(null, {error: 'Not allow access!'});
}
const user = await new User({
googleId: profile.id,
email: profile.emails[0].value,
name: profile.displayName,
avatar: profile.photos[0].value,
}).save();
done(null, user);
},
),
);
And I'm writing logic code like that:
if (!profile._json.domain || profile._json.domain !== 'framgia.com') {
return done(null, {error: 'Not allow access!'});
}
But I think it won't work, but I don't know how to handle the error and send the message back to user.
My routes:
const passport = require('passport');
module.exports = (app) => {
app.get(
'/auth/google',
passport.authenticate('google', {
scope: ['profile', 'email'],
}),
);
app.get(
'/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
(req, res) => {
// Successful authentication, redirect home.
res.redirect('/');
},
);
};
How to handle the error and redirect to route /error with some message?
Any ideas would be greatly appreciated, thanks.
First of all, if you want to return the user only if an email has a certain domain, you need to put your domain check logic before findOne(). With current logic, if you found a user it will simply return it without checking the email domain
//check email domain before finding the user
if (!profile._json.domain || profile._json.domain !== 'framgia.com') {
return done(null, {error: 'Not allow access!'});
}
const existingUser = await User.findOne({ googleId: profile.id });
if (existingUser) {
return done(null, existingUser);
}
According to passport js documentation, http://www.passportjs.org/docs/configure/ (check verify callback section)
An additional info message can be supplied to indicate the reason for
the failure. This is useful for displaying a flash message prompting
the user to try again.
so if the domain does not match, you should return an error like this
return done(null, false, { message: 'Not allow access!' });

Passport-facebook returns only 'id' and 'displayName'

I am using passport-facebook to authenticate users. Post authentication, i receive only the 'id' and 'displayName' parameters. All the other fields are returned as undefined. I am specifically interested in the email. I don't see that returned in the profile at all. Appreciate any help in figuring out what I am missing.
config/auth.js:
module.exports = {
'facebookAuth' : {
clientID: 'MY_CLIENT_ID', // your App ID
clientSecret: 'MY_CLIENT_SECRET', // your App Secret
callbackURL: '/auth/facebook/callback',
profileFields: ['id', 'emails', 'displayName']
},
I have also tried changing 'emails' to 'email'
Routes:
app.get('/auth/facebook', passport.authenticate('facebook', {
scope: ['email', 'public_profile']
}));
app.get('/auth/facebook/callback',
passport.authenticate('facebook', {
successRedirect : '/profile',
failureRedirect : '/'
}));
Facebook Strategy:
passport.use(new FacebookStrategy({
clientID : configAuth.facebookAuth.clientID,
clientSecret : configAuth.facebookAuth.clientSecret,
callbackURL : configAuth.facebookAuth.callbackURL
},
function(token, refreshToken, profile, done) {
process.nextTick(function() {
console.log(util.inspect(profile, {showHidden: false, depth: null}))
User.findOne({ 'facebook.id' : profile.id }, function(err, user) {
if (err)
return done(err);
if (user) {
return done(null, user); // user found, return that user
} else {
// if there is no user found with that facebook id, create them
var newUser = new User();
newUser.facebook.id = profile.id; // set the users facebook id
newUser.facebook.token = token; // we will save the token that facebook provides to the user
newUser.facebook.name = profile.displayName; // look at the passport user profile to see how names are returned
// newUser.facebook.email = profile.emails[0].value; // facebook can return multiple emails so we'll take the first
// save our user to the database
newUser.save(function(err) {
if (err)
throw err;
// if successful, return the new user
return done(null, newUser);
});
}
});
});
}));
Profile information returned:
GET /auth/facebook 302 0.744 ms - 0
{ id: '10156351538257640',
username: undefined,
displayName: 'Nitin Vig',
name:
{ familyName: undefined,
givenName: undefined,
middleName: undefined },
gender: undefined,
profileUrl: undefined,
provider: 'facebook',
_raw: '{"name":"Nitin Vig","id":"10156351538257640"}',
_json: { name: 'Nitin Vig', id: '10156351538257640' } }
GET /auth/facebook/callback?code=AQAdi8Gs55Th1oXVXGLxNynm8s_rYs9XWC7IRB4hCk5xoVWCgPsRXS-DL6Q6ekI0bvb5osls77OWSfuTdCAIhkc2ntohRrG6ZDES3Nx907FdpjMzJzfCYJfXYeutBAkPwTbesjdcBsig_76VFWwV9WL-6X0jLngTtYLdkELWid9G5bTltc0HrmUEaVkf8w6LeaDHdbQ1tlxh5CP9P9W7vT_M6AVXSpGPFTaoLI8IO25jyS-Xm9bIprPQV1-eVYK_EHhcYZVaK2INnGeWhwTu-6P1MMFO4gPIFGgiKqSmREXnfS4C_JqgwpjEc0Z5PGbhLIE 302 16582.309 ms - 60
GET /profile 200 2.081 ms - 2204
I am using similar code with passport-google-oauth and that works fine.

passport-saml openidp login "failed to open stream"

Up front, I'll state that I am a SAML and Passport novice. I am attempting to use passport-saml in my node.js application for SAML authentication, but am getting a failure attempting to login through OpenIdP (the OpenIdP user configuration I have works correctly with the "passport-saml-example" application). The "login" through passport.authenticate to OpenIdP for my application is failing with the following error:
Exception: Error downloading metadata from "http://192.168.1.11:9050": file_get_contents(http://192.168.1.11:9050): failed to open stream: Connection timed out
Backtrace:
4 /www/openidp.feide.no/simplesamlphp/lib/SimpleSAML/Metadata/MetaDataStorageHandlerDynamicXML.php:235 (SimpleSAML_Metadata_MetaDataStorageHandlerDynamicXML::getMetaData)
3 /www/openidp.feide.no/simplesamlphp/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php:274 (SimpleSAML_Metadata_MetaDataStorageHandler::getMetaData)
2 /www/openidp.feide.no/simplesamlphp/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php:310 (SimpleSAML_Metadata_MetaDataStorageHandler::getMetaDataConfig)
1 /www/openidp.feide.no/simplesamlphp/modules/saml/lib/IdP/SAML2.php:296 (sspmod_saml_IdP_SAML2::receiveAuthnRequest)
0 /www/openidp.feide.no/simplesamlphp/www/saml2/idp/SSOService.php:19 (N/A)
My passport-saml configuration is as follows:
passport : {
strategy : 'saml',
saml : {
entryPoint : 'https://openidp.feide.no/simplesaml/saml2/idp/SSOService.php',
issuer : 'http://192.168.1.11:9050',
callbackUrl : 'http://192.168.1.11:9050/login/callback'
}
},
My route configuration for login is as follows:
// "login" route
app.get("/login",
passport.authenticate(config.passport.strategy, {
successRedirect : "/",
failureRedirect : "/login"
})
);
// "login/callback" route
app.post('/login/callback', function (req, res) {
passport.authenticate(config.passport.strategy,
{
failureRedirect: '/',
failureFlash: true
});
res.redirect('/');
});
Here is the passport middleware setup:
passport.serializeUser(function (user, done) {
db.collection('users').find({email: user.email}).toArray(function (err, result) {
console.log("Passport serialize user: " + user);
if (result.length === 0) {
// User is not in the database, add the user.
var insertData = [{email: user.email, firstName: user.givenName, lastName: user.sn}];
db.collection('users').insert(insertData, function (err, result) {
done(null, insertData);
});
} else {
// User is already in the database, just return their data
done(null, result);
}
});
});
passport.deserializeUser(function (user, done) {
console.log("Passport de-serialize user: " + user);
db.collection('users').find({email: user.email}).toArray(function (err, result) {
console.log("Passport de-serialize result: " + result);
done(null, user);
});
});
passport.use(new SamlStrategy(
{
path : config.passport.saml.callbackUrl,
entryPoint : config.passport.saml.entryPoint,
issuer : config.passport.saml.issuer
},
function (profile, done) {
console.log("Returning SAML authentication: " + profile);
return done(null,
{
id : profile.uid,
email : profile.email,
displayName : profile.cn,
firstName : profile.givenName,
lastName : profile.sn
});
}
));
I believe this is all very similar to the passport-saml-example configurations I have seen; any ideas on what I am missing in this configuration?
Issue was due to problems with the SP registration on the IDP.

How to differentiate Login from Sign up when using PassportJS for Facebook

I am trying to set up goals on Google Analytics to track Sign Ups, so I set up a 'thank you ' page as my url goal. It works well when my users sign up with their email address but not when they use facebook to sign up/login. When they login, they are redirected to the thank you page as there is only one url callback when setting up Facebook using Passport JS and Node.
Here is my code:
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(id, done) {
UserActivity.findOne(id,'uid ref', function (err, user) {
done(err, user);
});
});
passport.use(new FacebookStrategy({
clientID: 'XXXXXXXXXXXX',
clientSecret: 'XXXXXXXXXXXXXXXX',
callbackURL: "https://www.xxxxxxx.com/auth/facebook/callback"
},
function(accessToken, refreshToken, profile, done) {
//console.log(profile);
User.findOne({ uid: profile.id }, function(err, uInfo) {
if(err) console.log('Error: '+err);
else{
//User exists: we are done
if(uInfo){
done(err, uInfo);
}
else{
//User doesn't exist: we create a new one
var newUser = new User ({
uid: profile.id,
email:profile.emails[0].value,
ref: 'Facebook'
});
// Saving it to the database.
newUser.save(function (err,uInfo) {
if (err) console.log ('Error on save!');
done(err, uInfo);
});
}
}
})
}
));
app.get('/auth/facebook', passport.authenticate('facebook',{ scope: 'email' }));
app.get('/auth/facebook/callback',
passport.authenticate('facebook', { successRedirect: '/thankyou',
failureRedirect: '/login' }));
If the user exists, I would like to redirect to their dashboard ('/dashboard) and if they are new users, I need to redirect them to /thankyou.
Any idea how to achieve this?
Thanks a lot!
Nevermind, found the answer. Here is the updated code below. Pay attention to the use of passReqToCallback and req.session.newu
passport.use(new FacebookStrategy(
{
clientID: 'XXX',
clientSecret: 'XXX',
callbackURL: "https://www.XXX.co/auth/facebook/callback",
passReqToCallback: true
},
function(req, accessToken, refreshToken, profile, done) {
//console.log(profile);
User.findOne({ uid: profile.id }, function(err, uInfo) {
if(err) console.log('Error: '+err);
else{
if(uInfo){
done(err, uInfo);
}
else{
var newUser = new User ({
uid: profile.id,
email:profile.emails[0].value,
ref: 'Facebook'
});
// Saving it to the database.
newUser.save(function (err,uInfo) {
if (err) console.log ('Error on save!');
req.session.newu=true;
done(err, uInfo);
});
}
}
})
}
));
app.get('/auth/facebook', passport.authenticate('facebook',{ scope: 'email' }));
app.get('/auth/facebook/callback',function(req, res, next) {
passport.authenticate('facebook', function(err, user, info) {
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.logIn(user, function(err) {
if (err) { return next(err); }
var redLink = '/dashboard';
if(req.session.newu)redLink = '/dashboard?newu'
return res.redirect(redLink);
});
})(req, res, next);
});
An existing user will be redirected to /dashboard and a new user will be redirected to /dashboard?newu
Google Analytics doesn't need 2 different urls, it just needs a query string. When I set up the url goal, I selected url start with /dashboard?newu.
Hope this helps
The question is a bit old but it might still help someone like me. The OP's answer works but it means you have to take care of log-in user and session, etc. In case you still want to leave those work to PassportJS, use req.session.returnTo in strategy's callback with successReturnToOrRedirect option in passport.authenticate() would work.

Resources