I have a MERN web application where the client is hosted on Netlify and the server on Heroku. I have user authentication through Passport.js using Google OAuth 2.0. In particular, I have it so that if a user successfully logs in, they are redirected to the route /auth/protected where I make use of that user's email. If authentication fails, they are simply logged out and redirected to a page on the frontend.
The authentication works perfect locally (before deployment). After deploying, when Google successfully authenticates a user, I get an internal server error because req.user is null, and so I am unable to obtain the email (Heroku error: TypeError: Cannot read properties of undefined (reading 'emails')). I have been unable to figure out why req.user is null after deployment. I have successfully verified my OAuth Consent Screen on the Google cloud, have included the correct URIs, have the publishing status as "In production," and included both the client and server URLs in the "Authorized JavaScript origins."
Thank you for your time and help :)
My authentication file (I have not connected to mongoose/mongodb here because I am not storing users there):
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const passport = require("passport");
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CLIENT_URL,
scope: ["email"],
},
function (accessToken, refreshToken, profile, cb) {
cb(null, profile);
}
)
);
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (user, done) {
done(null, user);
});
Relevant parts of my server:
const express = require("express");
const mongoose = require("mongoose");
const dotenv = require("dotenv");
const cors = require("cors");
const bodyParser = require("body-parser");
require("./auth");
const passport = require("passport");
const session = require("express-session");
var path = require("path");
// URI Configuration
dotenv.config();
// App Init
const app = express();
// DB Connection
mongoose.connect(process.env.DB_URI);
// Middleware
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(
cors({
origin: "THE_ORIGIN_LINK",
methods: "GET,POST,PUT,DELETE",
credentials: true,
})
);
app.use(
session({
secret: process.env.SESSION_SECRET,
resave: true,
saveUninitialized: false,
cookie: { secure: true },
})
);
app.use(passport.initialize());
app.use(passport.session());
Relevant parts of authentication:
const passport = require("passport");
// User's email
var email;
// Send google authentication
const getAuthentication = passport.authenticate("google", ["email"]);
// Google callback after authentication
const getCallback = passport.authenticate("google", {
// URL to bring to user to upon success
successRedirect: "/auth/protected",
// URL to bring to user to upon failure
failureRedirect: "/auth/failure",
});
// Authentication success
const getSuccess = (req, res, next) => {
// Set email
email = req.user.emails[0].value;
// Check for the correct user
if (req.user.emails[0].value === process.env.AUTHENTICATION_EMAIL) {
res.redirect("REDIRECT_LINK");
} else {
getLogout(req, res, next);
}
};
// Authentication failure
const getFailure = (req, res, next) => {
// Delete session and cookie
req.session.destroy((err) => {
res.clearCookie("connect.sid");
res.redirect("REDIRECT_LINK");
});
};
// Logout
const getLogout = (req, res, next) => {
// Reset email variable
email = null;
// Logout
req.logout(function (err) {
if (err) {
console.log("Error logging out: " + err);
return next(err);
}
});
// Delete session and cookie
req.session.destroy((err) => {
res.clearCookie("connect.sid");
res.redirect("REDIRECT_LINK");
});
};
module.exports = {
getAuthentication,
getCallback,
getSuccess,
getFailure,
getLogout,
};