Passport facebook doesn't create new user and times out - node.js

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

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

How to handle multiple session when using multiple stratgies in PassportJs with ExpressJs?

I'm developping an app where I want the user to login with thier discord account (Need do some check; checking user guilds, and roles) then when the user met the requirements, then I can allow that user authenticate with twitter to get some permissions. The first created session(session.sid) is overided by the last strategy used.
I'm using Mongodb to store users sessions, twitter, and discord information. I want to keep the previous sessions for other provider.
Routes part:
router.get('/discord', passport.authenticate('discord'), (req, res) => {
res.send(200)
})
router.get('/discord/redirect', passport.authenticate('discord'), (req, res) => {
res.send({msg: "Success loging with discord..."})
})
router.get('/discord-status', (req, res) => {
return req.user ? res.send(req.user):res.send({error: 403, message: 'Unauthorized user.'
})
})
router.get('/twitter', passport.authenticate('twitter', { failureRedirect: '/login', failureMessage: true }),
function(req, res) {
res.redirect('/');
})
router.get('/twitter/redirect', passport.authenticate('twitter'), (req, res) => {
res.send({msg: "Success loging with twitter..."})
})
router.get('/twitter-status', (req, res) => {
console.log(req.user)
return req.user ? res.send(req.user):res.send({error: 403, message: 'Unauthorized user.'
})
})
Discord passport part:
import passport from 'passport';
import { Profile, Strategy } from 'passport-discord';
import { VerifyCallback } from 'passport-oauth2';
import { User } from '../database/schemas';
import { config } from "dotenv"
// get env variables
config()
passport.serializeUser((user: any, done) => {
console.log(`Serialization: ${user} - ${user.id}`)
return done(null, user.id);
});
passport.deserializeUser(async (id: string, done) => {
console.log(`Deserialization: ${id}`)
try {
const user = await User.findById(id);
return user ? done(null, user) : done(null, null);
} catch (err) {
console.log(err);
return done(err, null);
}
});
passport.use(
new Strategy(
{
clientID: process.env.DISCORD_CLIENT_ID!,
clientSecret: process.env.DISCORD_CLIENT_SECRET!,
callbackURL: process.env.DISCORD_CALLBACK_URL,
scope: ['identify', 'email', 'guilds'],
},
async (
accessToken: string,
refreshToken: string,
profile: Profile,
done: VerifyCallback
) => {
const { id: discordId } = profile;
try {
const existingUser = await User.findOneAndUpdate(
{ discordId },
{ accessToken, refreshToken },
{ new: true }
);
if (existingUser) return done(null, existingUser);
const newUser = new User({ discordId, accessToken, refreshToken });
const savedUser = await newUser.save();
return done(null, savedUser)
// TO NOT AUTHORIZE THE USER WHEN ISN'T IN GUILD
// AND HASN'T THE REQUIRED ROLE
// return done(null, false)
} catch (err) {
console.log(err);
return done(err as any, undefined);
}
}
)
);
Twitter passport part:
const passport = require('passport')
import {config} from "dotenv"
var Strategy = require('passport-twitter');
import { TwitterAccounts } from '../database/schemas';
// get env variables
config()
passport.serializeUser((user: any, done: any) => {
console.log(`Serialization: ${user} - ${user.id}`)
return done(null, user.id);
});
passport.deserializeUser(async (id: string, done: any) => {
console.log(`Deserialization: ${id}`)
try {
const user = await TwitterAccounts.findById(id);
return user ? done(null, user) : done(null, null);
} catch (err) {
console.log(err);
return done(err, null);
}
});
passport.use(
new Strategy(
{
consumerKey: process.env.TWITTER_CONSUMER_KEY,
consumerSecret: process.env.TWITTER_CONSUMER_SECRET_KEY!,
callbackURL: process.env.TWITTER_CALLBACK_URL,
},
async (
token: any,
tokenSecret: any,
profile: any,
cb: any
) => {
const { id: twitterId, username: userName } = profile;
try {
const existingUser = await TwitterAccounts.findOneAndUpdate(
{ twitterId },
{ token, tokenSecret, userName},
{ new: true }
);
console.log({ twitterId }, { token, tokenSecret, userName})
if (existingUser) return cb(null, existingUser);
const newUser = new TwitterAccounts({ twitterId, token, tokenSecret, userName });
const savedUser = await newUser.save();
return cb(null, savedUser)
} catch (err) {
console.log(err);
return cb(err as any, undefined);
}
}
)
);
The discord part is working and the session persist:
Session {
cookie: {
path: '/',
_expires: 2023-10-25T15:47:55.818Z,
originalMaxAge: 31104000000,
httpOnly: true
},
passport: { user: '635e9cab54916ceacbb2035c' }
}
but for the twitter it isn't
Session {
cookie: {
path: '/',
_expires: 2023-10-25T15:38:35.503Z,
originalMaxAge: 31104000000,
httpOnly: true
},
passport: {}
}
As you see the passport key is empty.
How to handel multiple cookie please, any help or advice will be appreciated. Thanks

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

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

Resources