PassportJS Google oauth2.0 login/register - node.js

I've been trying to get the google oauth login system to work on multiple ports and found issues of saving the data (from backend to frontend) when redirecting (most tutorials do this on the same port which got me confused). I understand how to get data from backend on frontend (through axios) but am confused how to send data from backend to frontend, and so I found this tutorial similar to what I'm looking for. (I tried to adapt this to use google oauth instead of twitter)
However, in this tutorial during the google redirect, I'm getting an error:
Error: Failed to serialize user into session
On googling, most say it's related to the serializeUser and deserializeUser functions (which I however have). So I'm quite hard-stuck and am hoping to find a fix.
Here's my code:
passport.serializeUser((user, done) => {
done(null, user.id);
});
// deserialize the cookieUserId to user in the database
passport.deserializeUser((id, done) => {
User.findById(id)
.then(user => {
done(null, user);
})
.catch(e => {
done(new Error("Failed to deserialize an user"));
});
});
passport.use(
new GoogleStrategy(
{
clientID: keys.googleClientID,
clientSecret: keys.googleSecret,
callbackURL: "http://localhost:5000/auth/google/redirect"
},
(token, tokenSecret, profile, done) => {
// find current user in UserModel
console.log(profile);
const currentUser = User.findOne({
googleId: profile.id
});
// create new user if the database doesn't have this user
if (!currentUser) {
const newUser = new User({
name: profile.displayName,
screenName: profile.displayName,
googleId: profile.id,
profileImageUrl: ""
})
newUser.save()
.catch((err) => console.log(err));
if (newUser) {
done(null, newUser);
}
} else {
done(null, currentUser);
}
}
)
);
Thanks for reading this and helping!!

Related

passport-local-mongoose and Google Auth, how handle mutltiple serializers with Passport.js?

how to handle provider strategy and local strategy with passport.js using passport-local-mongoose?
Here I am defining my strategies and de/serializers
// For my Local strategy
passport.serializeUser(Admin.serializeUser());
passport.deserializeUser(Admin.deserializeUser());
// For my Google strategy
passport.serializeUser((user, done) => {
done(null, user.id); //user.id is the id from Mongo
});
passport.deserializeUser((id, done) => {
User.findById(id).then(user => {
done(null, user);
});
});
passport.use('google',
new GoogleStrategy(
{
clientID: keys.googleClientID,
clientSecret: keys.googleClientSecret,
callbackURL: "/auth/google/callback",
proxy: true
},
async (accessToken, refreshToken, profile, done) => {
const existingUser = await User.findOne({ googleId: profile.id });
if (existingUser) {
done(null, existingUser);
} else {
const user = await new User({ googleId: profile.id, token: accessToken, name:profile.displayName }).save();
done(null, user);
}
}
)
);
passport.use('local', new LocalStrategy(Admin.authenticate()));
Like you see I have 2 de/serializers and this is a problem and that is not working but if I take just one serializer and one deserializer for each strategy that is working
so the question is how to handle Local and Google strategy with Passport.Js and node.js
This what I am trying to do
passport.serializeUser(function(user, done) {
if(// This is Local Strategy){
User.serializeUser();
}
else{
done(null, user);
}
});
passport.deserializeUser(function(user,id, done) {
if(// This is Local Strategy){
User.serializeUser();
}
else{
User.findById(id).then(user => {
done(null, user);
});
}
});
UPDATE :
I found a post passport.js multiple de/serialize methods with more information about multiple de/serializers and I tried this :
passport.serializeUser((user, done) => {
done(null, user.id); //user.id is the id from Mongo
});
passport.deserializeUser((id, done) => {
User.findById(id)
.then(user => {done(null, user)
// .catch((err) => done('pass'));
});
});
passport.deserializeUser((obj, done) => {
Admin.deserializeUser();
});
But the catch block mentioned in this post not working for me
From your update:
passport.serializeUser((user, done) => {
done(null, user.id); //user.id is the id from Mongo
});
passport.deserializeUser((id, done) => {
User.findById(id)
.then(user => {
done(null, user)
})
.catch((err) => done('pass')); //you should use catch like this
});
passport.deserializeUser((obj, done) => {
Admin.deserializeUser();
});
If it still does not work,
Follow my code: passport config

failed to retrieve accessToken from instagram with passportJS

I am setting up my website with Passport-Instagram and I continue getting an error: "InternalOAuthError: Failed to obtain access token". I am simply trying to console.log my accessToken and my profile info when I try logging in but no matter which block of code I use whether it is jaredhansons GitHub for passport-instagram, I can not seem to obtain the access Token.
The first time that I used these setups it did actually redirect me to the Instagram login page but after the first time on a browser it just sends me this failed to obtain access token error.
I have tried using jared hanson's setup https://github.com/jaredhanson/passport-instagram/blob/master/examples/login/app.js
I have tried using https://medium.com/crowdbotics/add-instagram-login-to-your-nodejs-app-using-passportjs-be7e8e31efb8
//passport.js file
passport.use(new InstagramStrategy({
clientID: keys.instagramClientID,
clientSecret: keys.instagramClientSecret,
callbackURL: '/auth/instagram/callback',
proxy: true
}, (accessToken, refreshToken, profile, done) => {
console.log(accessToken);
console.log(profile);
//User.findOrCreate({ instagramId: profile.id }, function (err, user) {
return done(err, user);
//});
}
));
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id)
.then(user => done(null, user));
});
//auth.js file
router.get('/instagram', passport.authenticate('instagram'))
router.get('/instagram/callback',
passport.authenticate('instagram', { failureRedirect: '/' }),(req, res) => {
res.redirect('/dashboard');
});
I expect to console.log the accessToken given by Instagram.
GitHub repo: https://github.com/ValverdeDaniel/BRv2

Error in facebook login from mobile browser

I am trying to login by Facebook using "react-facebook-login" and "passport-facebook-token" in nodejs.
I logged successfully in the web browser but i am getting an error "URL Bloocked" in mobile browser.
When i am switching to desktop mode on my phone its successfully login.
const module.exports = passport => {
passport.use(
"facebookStrategy",
new FacebookStrategy(
{
clientID: *****,
clientSecret: "*****",
},
(accessToken, refreshToken, profile, done) => {
User.findOne({ email: profile.emails[0].value })
.then(user => {
if (user) {
return done(null, user);
} else {
//create new profile
const newProfile = new Profile({
user: newUser.id,
username: profile.displayName,
email: profile.emails[0].value,
profilepic: profile.photos[0].value
});
newProfile
.save()
.catch(err =>
console.log("Error in creating new profile " + err)
);
return done(null, newProfile);
}
})
.catch(err => console.log(err));
}
)
);
};
This is common issue among mobile browsers and you will find that it would also be in some android devices.
For precaution it is under best practice to build flow of login manually. Go here
https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow
So yes, you will need to go with manual integration. But, it will be full-proof integration without doubts.

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

Nodejs Testing Authentication but getting a blank screen in browser

I finished wiring up my authentication flow by enabling cookies inside my application and then essentially telling passport to use cookies inside my authentication.
To test this out, I added a new route handler inside of my application whose sole purpose is to inspect this req.user property.
This is my services/passport.js file:
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() is a generic register to make Passport
// aware of new strategy
// creates a new instance to authenticate users
passport.use(
new GoogleStrategy(
{
clientID: keys.googleClientID,
clientSecret: keys.googleClientSecret,
callbackURL: '/auth/google/callback'
},
(accessToken, refreshToken, profile, done) => {
User.findOne({ googleId: profile.id }).then(existingUser => {
if (existingUser) {
// we already have a record with given profile id
} else {
// we dont have a user record with this id, make a new record
done(null, existingUser);
new User({ googleId: profile.id })
.save()
.then(user => done(null, user));
}
});
}
)
);
The data is passed to passport which pulls out the id out of the cookie data. The id is then passed on to my deserializeUser function where I am taking that id and turning it into a user model instance and then the whole goal is that the user model instance returned from deserializeUser is added to the request object as req.user and so that new route handler mentioned above has a job of inspecting req.user.
So in my routes/authRoutes.js:
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'));
app.get('/api/current_user', (req, res) => {
res.send(req.user);
});
};
So this is supposed to test that someone who has already gone through the OAuth flow in theory, can now log back in and we can authenticate that is the same user.
So the expected behavior being that I would once again go through the OAuth flow by visiting localhost:5000/auth/google and then open a separate browser in localhost:5000/api/current_user and be able to see the MongoDB record of that user as json in the browser, but instead I got a blank page and no error in my command line terminal or anywhere else.
What could be the matter here?
You have a minor flaw i've noticed in your if statement:
if (existingUser) {
// we already have a record with given profile id
} else {
// we dont have a user record with this id, make a new record
done(null, existingUser);
new User({ googleId: profile.id })
.save()
.then(user => done(null, user));
}
should be:
if (existingUser) {
// we already have a record with given profile id
done(null, existingUser);
} else {
// we dont have a user record with this id, make a new record
new User({ googleId: profile.id })
.save()
.then(user => done(null, user));
}

Resources