Failed to serialize user into session with passportjs and cookie-session - node.js

I'm trying to get authentication with Google+ OAuth. To achieve this I'm using passportjs with Google strategy (passport-google-oauth20 module) but I'm stuck in an error while passport tries to serialize the user into a session (using cookie-session).
The error comes after login in the Google site.
The code:
passport.serializeUser((user, done) => {
console.log('serialize ' + (user.id == undefined ? null : user.id));
console.log(user);
return done(null, (user.id == undefined ? null : user.id));
});
passport.deserializeUser((id, done) => {
console.log('dserialize id ' + id);
db.connect((err, client, don) => {
if (err) throw err
client.query('SELECT * FROM "AppUsers" WHERE "googleId" = $1', [id], (err, res) => {
don();
if (err) {
console.log(err.stack);
} else {
console.log(res.rows[0]);
if (res.rows[0]) {return done(null, res.rows[0]);}
else {return done(null, null);}
}
});
});
});
Edit:
async function checkGoogle(profile) {
const client = await db.connect();
try {
const { rows } = await client.query('SELECT * FROM "AppUsers" WHERE "googleId" = $1', [profile.id]);
let currentUser = rows[0];
console.log(currentUser);
if (currentUser) {
console.log('in db ' + currentUser.id);
console.log(currentUser);
return currentUser;
} else {
const { rows } = await client.query('INSERT INTO "AppUsers" ("googleId") VALUES ($1) RETURNING *', [profile.id]);
let newUser = rows[0];
console.log('not in db ' + newUser.id);
console.log(newUser);
return newUser;
}
} catch (error) {
alert(error);
} finally {
client.release();
}
}
passport.use(
new GoogleStrategy({
// options for google strategy
clientID: keys.google.clientID,
clientSecret: keys.google.clientSecret,
callbackURL: '/auth/google/redirect'
}, (accessToken, refreshToken, profile, done) => {
// check if user already exists in our own db
return done(null, checkGoogle(profile));
})
);
Output:
The error screen
Please tell me if you need more information about.

You need to wait for the checkGoogle function to return data by using async/await.
passport.use(
new GoogleStrategy({
// options for google strategy
clientID: keys.google.clientID,
clientSecret: keys.google.clientSecret,
callbackURL: '/auth/google/redirect'
}, async (accessToken, refreshToken, profile, done) => {
const user = await checkGoogle(profile);
// check if user already exists in our own db
return done(null, user);
})
);

Related

Passport.js GoogleStrategy not working! Getting error "Cannot read properties of undefined (reading '0')"

I am trying to implement passport.js google login, but as you can see in my code below I am having an issue with the profile object which is not behaving like the documentation says it should. Please help.
Also, I do not wish to you passports build in session support! I have my own functions for creating sessions and authenticating if the user is logged in.
const passport = require('passport');
const GoogleStrategy = require('passport-google-oidc');
const User = require('../models/user-schema');
async function create_user(name, email) {
// too long to show, just assume it works (not relevant for my question anyways)
const user = await User.create({});
return login_user(user)
}
function login_user(user) {
req.session.user_id = user._id;
delete user._doc.hash;
delete user._doc.salt;
return user;
}
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SEC,
callbackURL: 'http://localhost:4000/auth/login-google/redirect/',
scope: [ 'email', 'profile' ]
},
async function(request, accessToken, refreshToken, profile, done) {
try {
console.log(profile); // Gives a long string for some reason
console.log(profile.id); // Gives undefined even though it is used in the documentation
const email = profile.emails[0].value; // Here it throws the error even though this is what I have seen others use
// (!)
// Everything beyond this point I havent had the chance to test yet because the profile object, well I guess, ISNT AN OBJECT!
// (!)
const existing_user = await User.findOne({ email: email });
const user = (existing_user) ? login_user(existing_user) : await create_user(profile.displayName, email);
return done(null, user);
}
catch (err) {
console.log('errror: ' + err.message);
return done(err);
}
}));
router.get('/login-google', passport.authenticate('google'));
router.get('/login-google/redirect/', passport.authenticate('google', {
successRedirect: '/login-google/success',
failureRedirect: '/login-google/failure'
}));
router.get('/login-google/success', (req, res) => {
console.log('success');
});
router.get('/login-google/failure', (req, res) => {
console.log('failure');
});
You're importing GoogleStrategy from passport-google-oidc which has a different signature. The current signature of your implementation belongs to GoogleStrategy from passport-google-oauth2.
According to the passport's documentation for passport-google-oidc, your function's signature should be something like this:
var GoogleStrategy = require('passport-google-oidc');
passport.use(new GoogleStrategy({
clientID: process.env['GOOGLE_CLIENT_ID'],
clientSecret: process.env['GOOGLE_CLIENT_SECRET'],
callbackURL: 'https://www.example.com/oauth2/redirect'
},
function verify(issuer, profile, cb) {
db.get('SELECT * FROM federated_credentials WHERE provider = ? AND subject = ?', [
issuer,
profile.id
], function(err, cred) {
if (err) { return cb(err); }
if (!cred) {
// The account at Google has not logged in to this app before. Create a
// new user record and associate it with the Google account.
db.run('INSERT INTO users (name) VALUES (?)', [
profile.displayName
], function(err) {
if (err) { return cb(err); }
var id = this.lastID;
db.run('INSERT INTO federated_credentials (user_id, provider, subject) VALUES (?, ?, ?)', [
id,
issuer,
profile.id
], function(err) {
if (err) { return cb(err); }
var user = {
id: id.toString(),
name: profile.displayName
};
return cb(null, user);
});
});
} else {
// The account at Google has previously logged in to the app. Get the
// user record associated with the Google account and log the user in.
db.get('SELECT * FROM users WHERE id = ?', [ cred.user_id ], function(err, user) {
if (err) { return cb(err); }
if (!user) { return cb(null, false); }
return cb(null, user);
});
}
}
})
));

Passport facebook doesn't create new user and times out

I'm using passport with multiple strategies (JWT, Google and Facebook). All work fine except Facebook, when an existing user tries to login. In this case it just times out and I get a 504.
It tries to call https://api.example.com/users/facebook/callback/?code=... before I get the timeout error.
I tried the exact same logic from my Google strategy, where everything works fine, but it doesn't help. I tried different online tutorials but none of them worked either.
So what am I doing wrong?
Passport.js config - Facebook code block
function(passport) {
passport.serializeUser((user, cb) => {
cb(null, user);
});
passport.deserializeUser((user, cb) => {
cb(null, user);
});
passport.use(new FacebookStrategy({
proxy: true,
clientID: keys.facebook.clientID,
clientSecret: keys.facebook.clientSecret,
callbackURL: "https://api.example.com/users/facebook/callback",
profileFields: ['id', 'displayName', 'email']
},
async (accessToken, refreshToken, profile, done) => {
const { email, first_name } = profile._json;
try {
const oldUser = await User.findOne({ email: email });
if (oldUser) {
return done(null, oldUser);
}
} catch (err) {
console.log(err);
return done(null, false);
}
// register user
try {
const newUser = await new User({
facebook: true,
email: email,
name: first_name,
verified: true,
}).save();
done(null, newUser);
} catch (err) {
console.log(err);
return done(null, false);
}
}
))
}
User auth route
// FACEBOOK
router.get("/facebook", passport.authenticate("facebook"));
router.get("/facebook/callback", generalTooManyRequests, passport.authenticate("facebook"), (req, res) => {
const referer = req.cookies["Origin"]
let redirectURL
// login did NOT work!
if (!req.user) {
redirectURL = "https://app.example.com/login/fehler-facebook"
if (referer === "website") {
redirectURL = "https://example.com/login/?fehler-facebook"
}
res.redirect(redirectURL)
}
// login did work!
else {
redirectURL = "https://app.example.com/callback/token="
if (referer === "website") {
redirectURL = "https://example.com/callback/?token="
}
const tokenObject = utils.issueJWT(req.user);
res.redirect(redirectURL + tokenObject.token)
}
});

PassportJs Google Auth saves existing user as a new user in the database. How can I fix that?

I'm using passportJs Google Authetication. Although a user exist in database, When I login the system with this user, It creates this user again in the database as a new user. How can I fix this problem, can you help ?
Thats image of the database:
Here is my codes:
module.exports = passport.use(
new GoogleStrategy(
{
clientID: config.google.clientID,
clientSecret: config.google.clientKey,
callbackURL: "/auth/google/callback",
},
async (accessToken, refreshToken, profile, done) => {
try {
const user = await models.User.findOne({ google: { id: profile.id } });
if (user) {
done(null, user);
} else {
const newUser = new models.User({
google: profile,
isSocialAuth: true,
name: profile.name.givenName,
lastName: profile.name.familyName,
cart: { items: [] },
});
await newUser.save();
done(null, newUser);
}
} catch (error) {
done(error, null);
}
}
)
);
passport.serializeUser((user, done) => {
done(null, user._id);
});
passport.deserializeUser((id, done) => {
models.User.findById(id, (err, user) => done(err, user));
});
My Router:
router.get("/auth/google", passport.authenticate("google", { scope: ["profile"] }));
router.get("/auth/google/callback", passport.authenticate("google", { failureRedirect: "/login" }), async (req, res) => {
req.session.user = req.user;
req.session.isAuthenticated = true;
res.redirect("/");
});
module.exports = router;
My UserSession Middleware:
module.exports = (req, res, next) => {
if (!req.session.user) {
return next();
}
models.User.findById(req.session.user._id)
.then((user) => {
req.user = user;
next();
})
.catch((err) => {
console.log(err);
});
};
After signing in, in the Passport part,
the findOne query might have some issue. It is not able to find the user & hence it is registering again.
Replace
const user = await models.User.findOne({ google: { id: profile.id } });
to
const user = await models.User.findOne({ "google.id": profile.id });
& check if it works.

callback for google oauth not redirecting

My google oauth is working properly, the data is saved and all the console.logs answer everywhere but there is one problem after serializing my webpage is not redirecting to the react application
problem is in callback
Router.get(
"/callback",
passport.authenticate("google"),
(req, res, err) => {
if (err.name === "TokenError") {
res.redirect("/"); // redirect them back to the login page
}
user = req.user;
console.log("[googleRoutes.js] callback : ", user);
res.redirect("localhost:3000");//problem lies here
}
);
anthe other code is :
const passport = require("passport"),
GoogleStrategy = require("passport-google-oauth20").Strategy,
keys = require("../key"),
User = require("../User"),
UserSession = require("../UserSession");
passport.serializeUser((user, done) => {
console.log("[Serializing Google] :", user.id);
done(null, user.id);
});
passport.deserializeUser((user, done) => {
User.findById(user.id).then(user => {
console.log("[Deserializing Google] :", user.id);
done(null, user);
});
});
passport.use(
new GoogleStrategy(
{
clientID: keys.googleClientID,
clientSecret: keys.googleClientSecret,
callbackURL: "/auth/google/callback"
},
async (accessToken, refreshToken, profile, done) => {
console.log("profile :", profile.id);
const existingUser = await User.findOne({ googleId: profile.id });
if (existingUser) {
//we already have a record wih the given profile ID
console.log("user existing google");
done(null, existingUser);
} else {
//we don't have a user record with this ID, make a new record
const user = await new User();
user.username = profile.displayName;
user.googleId = profile.id;
console.log("google id is saved");
user.save().then(theuser => {
done(null, theuser);
});
}
}
)
);

Return accesstoken after Oauth Authentication in passport.js

I want the following flow in my app
User selects authenticate with google
I redirect to google and get the callback (This happens using Passport)
I now want to return an auth token that is specific to my application in the form of response to user.
var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
passport.use(new GoogleStrategy({
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
callbackURL: "http://127.0.0.1:3000/auth/google/callback"
},
function(accessToken, refreshToken, profile, done) {
// RETURN ACCESS TOKEN IN THE RESPONSE
}
));
I do get the success callback, but I don know how to return a custom access token/response from the callback function.
I guess the answer related to using of done(), or serialization or deserialization which I am not clear from the docs or any examples. Can you please also explain how done(), or serialization or deserialization are used.
You can do this in serialization and deserialization. See this passport example
userSchema.methods.generateRandomToken = function () {
var user = this,
chars = "_!abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
token = new Date().getTime() + '_';
for ( var x = 0; x < 16; x++ ) {
var i = Math.floor( Math.random() * 62 );
token += chars.charAt( i );
}
return token;
};
var User = mongoose.model('User', userSchema);
//serialize
passport.serializeUser(function (user, done){
var createAccessToken = function() {
var token = user.generateRandomToken()
User.findOne({
accessToken: token
}, function(err, existingUser){
if (err) { return done(err) }
if (existingUser) {
createAccessToken()
} else {
user.set('accessToken', token)
user.save(function(err){
if (err) { return done(err) }
return done(null, user.get('accessToken'))
})
}
})
}
if (user._id) {
createAccessToken()
}
})
//deserialize
passport.deserializeUser(function(token, done){
User.findOne({
accessToken : token
}, function(err, user){
done(err, user)
})
})

Resources