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 callreturn done(null, foundUser)
then after the saving callreturn 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: