`Cannot set headers after they are sent to the client ` when I try to login with `passport-google-oauth20` - express

When I tried to implement Google OAuth into my node app using passport-google-oauth20, I got a problem.
Whenever I attempt the first login to the secrets page with the following code, I fail to authenticate and got redirected to the /login page, also got the error saying Cannot set headers after they are sent to the client at the line serializing the user, even though newUser has been saved in the mongoDB.
However, I can successfully authenticate and login to the secrets page the second login attempt.
What's happening behind the scenes where the error occurs? How can I successfully authenticate the user when the first login attempt?
I referred to this Q&A as well.
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "http://localhost:3000/auth/google/secrets"
},
(accessToken, refreshToken, profile, done) => {
User.findOne({ googleId: profile.id }, (err, foundUser) => {
if (err) return done(err);
if (!foundUser) {
const newUser = new User({
googleId: profile.id
});
newUser.save((err, savedUser) => {
if (err) throw err;
return done(null, savedUser);
});
}
return done(null, foundUser);
});
}
));
passport.serializeUser((user, done) => {
done(null, user.id); ///// The error occurs at this line /////
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
app.get('/auth/google',
passport.authenticate('google', { scope: ['profile'] }));
app.get(
"/auth/google/secrets",
passport.authenticate("google", {
successRedirect: "/secrets",
failureRedirect: "/login"
})
);
app.get("/secrets", (req, res) => {
if (req.isAuthenticated()) return res.render("secrets");
res.redirect("/login");
});

The issue I see is within the verify callback. Calling return done(null, savedUser) will occur asynchronously. This means that the program will first call return done(null, foundUser) then after the saving call return done(null, savedUser).
To resolve the issue I would recommend refactoring the verify callback to use async/await. This makes it easier to reason about and reduces the chances of race conditions from conflicting callbacks.
Example Refactor:
async (accessToken, refreshToken, profile, done) => {
try {
let foundUser = await User.findOne({ googleId: profile.id });
if (!foundUser) {
const newUser = new User({
googleId: profile.id
});
await newUser.save();
return done(null, newUser);
}
return done(null, foundUser);
} catch (err) {
return done(err);
}
}));

Related

How to save user data in signup/register using passport.js with express server

I am working to implement passport.js in my react/node/express/sequelize app.
I currently have middleware working for logging a user in, and checking if the user is authenticated. However, when a new user signs up or registers, their user data is not being saved to the server session (even though it is created in the DB). This means after a user registers, they have to go to the login page, enter their credentials and hit login before their session is saved.
My login function is simple:
router.post('/login', passport.authenticate('local'), (req, res) => {
//console.log(req);
console.log("Is authenticated: " + req.isAuthenticated());
res.json(req.user);
});
it uses passport.authenticate local strategy, which I've defined as:
passport.use(new LocalStrategy(
{
usernameField: 'email',
},
((email, password, done) => {
User.findOne({
where: {
email,
},
}).then((dbUser) => {
if (!dbUser) {
return done(null, false, {
message: 'Incorrect email.',
});
}
if (!dbUser.validPassword(password)) {
return done(null, false, {
message: 'Incorrect password.',
});
}
return done(null, dbUser);
});
}),
));
I know from the passport documentation and from looking at other questions that the passport.authenticate local strategy automatically calls the req.login() function, which serializes my user information and saves it in the server session.
My main issue is I'm not sure exactly how to implement this during my register function.
router.post('/signup', (req, res) => {
const user = {
first_name: req.body.first_name,
last_name: req.body.last_name,
email: req.body.email,
password: req.body.password,
};
User.findOrCreate({where: {email: user.email}, defaults: user})
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while creating the User."
});
});
});
I've tried calling req.login() after findOrCreate, but I get an error:
(node:42313) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
If I use my local strategy, I get an unauthorized response (since the credentials I'm using to authorize are not yet in the DB).
I figure I need to make a custom strategy for sign in, but it's not clear to me if that's the right approach, or how I would specify it.
I fixed this by adding req.login before res.send:
router.post('/signup', (req, res) => {
const user = {
first_name: req.body.first_name,
last_name: req.body.last_name,
email: req.body.email,
password: req.body.password,
};
User.findOrCreate({where: {email: user.email}, defaults: user})
.then(data => {
req.login(data[0], function(err) {
if (err) {
console.log("login function erroring out with: " + err)
}
});
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while creating the User."
});
});
});

Express passport.authenticate() not working properly

I'm having some issues with setting up passport. The information gets to the console.log(req.body) before passport.authenticate and then console.log(req.user) will return undefined afterwards. I will not hit the console.log inside of passport.use() function that is after the new LocalStrategy code. This does not though an error, nothing seems to happen. It will just enter the second if statement if(!user) and return me the status and error I outlined there. I have been trying to debug this for awhile and alas I'm no longer sure what the deal is.
this is what my auth file looks like
router.post("/login", (req, res, next) => {
console.log(req.body);
passport.authenticate("local", function (err, user, info) {
//console.log(req);
//console.log(user);
if (err) {
//console.log("cp1");
return res.status(400).json({ errors: err });
}
if (!user) {
return res.status(400).json({ errors: "No user found" });
}
req.logIn(user, function (err) {
console.log("cp1");
if (err) {
//console.log("cp3");
return res.status(400).json({ errors: err });
}
return res.status(200).json({ success: `logged in ${user.id}` });
});
})(req, res, next);
});
and this is what my passport.js file looks like
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(null, user);
});
});
passport.use(
new LocalStrategy((email, password, done) => {
console.log(`${email} , ${password}`);
db.User.findOne({ email: email })
.then((user) => {
if (!user) {
} else {
if (user.password === password) {
return done(null, user);
} else {
return done(null, false, { message: "Wrong Password" });
}
}
})
.catch((err) => {
return done(null, false, { message: err });
});
})
);
passport.initialize();
passport.session();
There is no such thing as req.user, I suppose you meant req.body.user or req.body.username depending on the JSON you send in the request.
I advice you to look at my repo below where I recently successfuly implemented Passport in Express:
https://github.com/fromb2b/saas

Passport local strategy is never get called

I know this question was asked many times in stack over flow. I tried every accepted answers but can't my local strategy into function. Here is my code
var express = require('express');
var app = express();
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
app.use(cookieParser()); // read cookies (needed for auth)
app.use(bodyParser.json({limit: '50mb'}));
app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
app.set('trust proxy', 1); // trust first proxy
app.use(session({
secret: '564sdf4as564f56a7s765s4afjkgadxjkbadksj',
resave: true,
saveUninitialized: true,
cookie: { secure: true }
}));
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy({
usernameField:'userName',
passwordField:'password',
passReqToCallback : true
},function(request, userName, password, done) {
console.log(request);
UserAccount.findOne({'userName': userName} , function(err, user) {
if (err) return done(err);
if (!user) return done(null, false, 'Incorrect username.' );
user.verifyPassword(password, function(err, isMatch) {
if (isMatch) {
return done(null, user);
} else {
return done(null, false, 'Incorrect password.');
}
});
});
}));
passport.serializeUser(function(user, done) {
console.log('Serialize user called');
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
console.log('Deserialize user called');
UserAccount.findById(id, function(err, user) {
done(err, user);
});
});
Then I created a router like
var router = express.Router();
require('./controllers/user')(router,passport);
app.use('/api',router);
Then in my user controller I created signIn function like
app.post('/signIn',function (request,response,next){
var variables = request.body;
console.log(variables);
passport.authenticate('local', function(error, user, info) {
console.log(user);
if (error) { console.log(error); return next(err); }
if (!user) { return response.redirect('/login'); }
response.logIn(user, function(err) {
if (err) { return next(err); }
return response.redirect('/users/' + user.username);
});
})(request, response, next);
});
Then I send a request from "Postman"
{
"userName":"karthik#abc.com",
"password":"qwerty"
}
My mongodb userName and password fields are same.
In my db there is an account with this user name and password. But every time it return 'user' as 'false' inside authenticate. I tried to console my request inside local strategy but it never gets called. I don't understand What I done wrong here? Can some one help to solve this? Thank you very much.
You should name local strategy and use it in authenticate.
Use like this passport.use('local-strategy',new LocalStrategy({});
and like passport.authenticate('local-strategy');

User in Passport Authentication Custom Callback is always false

I'm trying to use local authentication with Passport and Express, and I have two separate routes to log in: one for the website, which does a redirect, and one for the mobile app which I want to return a JSON object. The redirect route works. However, POSTing the same username and password to the other route receives the JSON response for a failed login. My LocalStrategy is as follows:
passport.use('local-signin', new LocalStrategy(
{passReqToCallback : true},
function(req, username, password, done) {
funct.localAuth(username, password)
.then(function (user) {
if (user) {
console.log("LOGGED IN AS: " + user.username);
req.session.success = 'You are successfully logged in ' + user.username + '!';
return done(null, user);
}
if (!user) {
console.log("COULD NOT LOG IN");
req.session.error = 'Could not log user in. Please try again.'; //inform user could not log them in
return done(null, false, { message: 'User not found' });
}
})
.fail(function (err){
console.log(err.body);
return done(err);
});
}
));
and these are the two places I use it:
app.post('/login', passport.authenticate('local-signin', {
successRedirect: '/',
failureRedirect: '/signin'
})
);
app.post('/api/login', function(req, res, next) {
passport.authenticate('local-signin', function(err, user, info) {
if (err) {
return res.json(user);
}
if (!user) {
return res.json(true);
}
req.logIn(user, function(err) {
if (err) {
return next(err);
}
return res.json(user);
});
})
(req, res, next);
});
I get the success redirect from './login', but './api/login' replies with 'true' (the JSON response I have set for if 'user' is 'false'). This doesn't make any sense to me, but I can't find any problem with the code.
Any idea what the issue here could be? Thanks.

req (request) is not defined for passportjs.use in SailsJS

I want to use req.flash("message" : error ) to support error message callbacks for passportJS inside of SailsJS. However, PassportJS does not handle the below during callbacks: (similar post: PassportJS Custom Authenticate Callback Not Called)
//there is no req found
req.flash("message" , "invalid password");
This usually will be alright if there's something like:
function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
//....
req.flash("message" , "invalid password");
}
But I can't use it inside passport.use.
/services/passport.js
passport.use(new HttpBasicStrategy(
function(username, password, done) {
// asynchronous verification, for effect...
process.nextTick(function () {
// Find the user by username. If there is no user with the given
// username, or the password is not correct, set the user to `false` to
// indicate failure. Otherwise, return the authenticated `user`.
findByUsername(username, function(err, user) {
if (err)
return done(null, err);
if (!user) {
return done(null, false, {
message: 'Unknown user ' + username
});
}
bcrypt.compare(password, user.password, function (err, res) {
if (!res){
--> //there is no req found
--> req.flash("message" , "invalid password");
return done(null, false, {
message: 'Invalid Password'
});
}
var returnUser = {
username: user.username,
createdAt: user.createdAt,
id: user.id
};
return done(null, returnUser, {
message: 'Logged In Successfully'
});
});
})
});
}
));
Is there another way to call req.flash? I'm pretty new at express and sailsjs, please pardon my ignorance.
For sails v0.10.x sails-generate-auth is perfect for this.