Passport googleStrategy callback redirect does not set session cookies in production

This issue has been driving me crazy for the past couple of days. I use passport google for my app (MERN stack) with the following flow:

  • User clicks the sign in with google button
  • Once user signs in, the google callback route is triggered
  • If the sign in is successful, the server redirects to either Page A or Page B on client depending on the user role
  • Both page A and B are protected and my authprovider calls the server to check if user is authenticated vefore sending the to page A or B

This works perfectly fine when in development, the session cookie is set when the google callback redirects to the client and the authentication by the authprovider is thus successful. However, when deployed to App Engine (server) and Netlify (client), the session cookie never seems to be set and thus user is always redirected to “https://stackoverflow.com/” page. The server and client are hosted on different domains.

What am I doing wrong here? How can I fix this or get around this?

Server.js

    const express = require("express");
    const mongoose = require('mongoose');
    const helmet = require("helmet");
    const cors = require("cors");
    const bodyParser = require("body-parser");
    require("dotenv").config();
    const cookieSession = require("cookie-session");
    const passport = require("passport");
    
    const authRoutes = require('./routes/auth');
    
    require('./passport/passportStrategies')(passport);
    
    
    const app = express();
    // Connect to MongoDB
    mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true });
    
    app.use(bodyParser.urlencoded({ extended: true }));
    app.use(bodyParser.json());
    
    app.use(helmet());
    app.use(cors({ origin: "https://mynetlifydomain.netlify.app", credentials: true }));
    app.use(express.json());
    
    app.use(
        cookieSession({
            maxAge: 24 * 60 * 60 * 1000,
            keys: [process.env.COOKIE_KEY],
            httpOnly: true,
            sameSite: 'none',
            secure: true,
            domain: 'https://mynetlifydomain.netlify.app/'
        })
    );
    
    app.use(passport.initialize());
    app.use(passport.session());
    
    app.use('/auth', authRoutes);
    
    const PORT = process.env.PORT || 5000;
    app.listen(PORT, () => {
        console.log(`Server is running on port ${PORT}`);
    });

passportStrategies.js

const GoogleStrategy = require('passport-google-oauth20').Strategy;
const LocalStrategy = require('passport-local').Strategy;
const BaseUser = require('../models/User');
const User = require('../models/RegularUser');
require('dotenv').config()

const GOOGLE_CALLBACK_URL = "https://appenginedomain.appspot.com/auth/google/callback";

module.exports = function (passport) {
    passport.use(
        new GoogleStrategy(
            {
                clientID: process.env.GOOGLE_CLIENT_ID,
                clientSecret: process.env.GOOGLE_CLIENT_SECRET,
                callbackURL: GOOGLE_CALLBACK_URL,
                passReqToCallback: true,
            },
            async (req, accessToken, refreshToken, profile, cb) => {
                try {
                    let user = await BaseUser.findOne({ email: profile.emails[0].value });
                    if (user) {
                        return cb(null, user);
                    }

                    user = await User.create({
                        name: profile.name.givenName,
                        lastName: profile.name.familyName,
                        email: profile.emails[0].value,
                        googleId: profile.id,
                        method: 'google',
                        company: '',
                        companyDomain: '',
                        profileCompleted: false,
                        phone: ''
                    });

                    return cb(null, user);
                } catch (err) {
                    console.log("Error signing up", err);
                    cb(err, null);
                }
            }
        )
    );

    passport.use(new LocalStrategy({ usernameField: 'email' }, async (email, password, done) => {
        try {
            const user = await BaseUser.findOne({ email: email });
            
            if (!user) return done(null, false);
            
            const isMatch = await user.comparePassword(password);
            
            if (!isMatch) return done(null, false);

            return done(null, user);
        } catch (err) {
            done(err);
        }
    }));

    passport.serializeUser((user, cb) => {
        console.log("Serializing user:", user);
        cb(null, user.id);
    });

    passport.deserializeUser(async (id, cb) => {
        const user = await BaseUser.findById(id);
        console.log("DeSerialized user", user);
        if (user) cb(null, user);
    });
};

auth.js (auth routes)

const express = require("express");
const passport = require("passport");
const BaseUser = require('../models/User');
const User = require('../models/RegularUser');
const { isUserAuthenticated } = require("../middlewares/auth");
const router = express.Router();

const BaseUrl = "https://mynetlifydomain.netlify.app";

router.post('/register', async (req, res) => {
    const { email, password, name, lastName, companyName } = req.body;

    try {
        const existingUser = await BaseUser.findOne({ email });
        if (existingUser) return res.status(400).json({ message: "Email already exists" });

        const user = new User(
            {
                name: name,
                lastName: lastName,
                email: email,
                password: password,
                method: 'local',
                company: companyName,
                companyDomain: '',
                profileCompleted: false,
                phone: ''
            }
        );
        await user.save();


        res.status(200).json({ isAuthenticated: true, user: { id, role, name, lastName, email, company } });
    } catch (error) {
        res.status(500).json({ error: true, msg: error.message });
    }
});

router.post('/login', passport.authenticate('local', { failureRedirect: errorLoginUrl }), (req, res) => {
    const { id, role, name, lastName, email, company } = req.user;
    res.status(200).json({ isAuthenticated: true, user: { id, role, name, lastName, email, company } });
});

router.get('/logout', (req, res) => {
    req.logout(); // This is Passport's method to logout
    req.session = null; // This will clear the session data from the cookie
    res.status(200).send("Logged out");
});


router.get("/authenticate", isUserAuthenticated, (req, res) => {
    const { id, role, name, lastName, email, company } = req.user;
    res.status(200).json({ isAuthenticated: true, user: { id, role, name, lastName, email, company } });
  });

router.get(
    "/google",
    passport.authenticate("google", { scope: ["profile", "email"] })
);

router.get(
    "/google/callback",
    passport.authenticate("google",  {
        failureMessage: "Cannot login to Google, please try again later!",
        failureRedirect: BaseUrl,
    }),
    (req, res) => {
        console.log("User: ", req.user);
        let url=""
        if (req.user) {
            // Check user role and redirect accordingly
            if (req.user.role === "admin") {
                res.redirect(BaseUrl + "/admin/objects");
            } else if (req.user.role === "user") {
                res.redirect(BaseUrl + "/user/objects");
            }
        } else {
            // Handle scenario where req.user is not set
            res.send("Thank you for signing in, but we couldn't identify your role!");
        }
    }
);

module.exports = router;

auth.js (middleware)

module.exports.isUserAuthenticated = (req, res, next) => {
    if (req.user) {
      next();
    } else {
      res.status(401).send("You must login first!");
    }
  };

Leave a Comment