I'm currently working on my first MERN project, with Passport Local, Google OAuth 2.0 and JWT. All my routes are protected with passport.authenticate("jwt", { session: false }), (req, res) => {})
. I know Google Strategy relies on Express session
and here's where my issue starts.
I don't know if this is the correct approach, but I want to generate a JWT token from Google OAuth login to handle the user session with it and keep all my routes requests working properly. From the past issues opened here I've found this one to be the most recent and practical solution, but it seemed to me that it would introduce a new problem - since the local login already generates the JWT token, wouldn't it conflict with the request on the step 4 from the linked solution? If I'm thinking right, the token would be generated two times when using the standard login - one right at the authentication and another when reaching the app's home page, not to mention that it looks like the request to /auth/login/success
will be made every time the home page is reached, which doesn't seem right to me.
So, what would be the best and cleaner way to approach this? I'd also like to validate everything on the server side and avoid appending the token to the URL, if possible. Anyway, here is my code:
passport.js
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const JwtStrategy = require("passport-jwt").Strategy;
const User = require("../models/user.model");
require("dotenv").config();
module.exports = function (passport) {
// CONFIGURE STRATEGIES
// Local
passport.use(User.createStrategy());
// Google
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "http://localhost:5000/auth/google/movie-log",
},
function (accessToken, refreshToken, profile, cb) {
User.findOrCreate(
{ googleId: profile.id },
{ first_name: profile.displayName, email: profile._json.email },
function (err, user) {
return cb(err, user);
}
);
}
)
);
// JWT
const cookieExtractor = (req) => {
let token = null;
if (req && req.cookies) {
token = req.cookies["access_token"];
}
return token;
};
passport.use(
new JwtStrategy(
{
jwtFromRequest: cookieExtractor,
secretOrKey: process.env.SECRET,
},
(payload, done) => {
User.findById(payload.sub, (err, user) => {
if (err) {
return done(err, false);
}
if (user) {
return done(null, user);
} else {
return done(null, false);
}
});
}
)
);
// CONFIGURE AUTHENTICATED SESSION PERSISTENCE
passport.serializeUser(function (user, done) {
done(null, user.id);
});
passport.deserializeUser(function (id, done) {
User.findById(id, function (err, user) {
done(err, user);
});
});
};
auth.js (for Google authentication)
const express = require("express");
const passport = require("passport");
const router = express.Router();
const login = "http://localhost:3000/login";
const diary = "http://localhost:3000/diary";
router.get(
"/google",
passport.authenticate("google", {
scope: ["email", "profile"],
})
);
router.get(
"/google/movie-log",
passport.authenticate("google", {
successRedirect: diary,
failureRedirect: login,
})
);
module.exports = router;
login.js (for local authentication)
const express = require("express");
const passport = require("passport");
const JWT = require("jsonwebtoken");
const router = express.Router();
const signToken = (userID) => {
return JWT.sign(
{
iss: "Movie.log",
sub: userID,
},
process.env.SECRET,
{ expiresIn: "1h" }
);
};
router
.route("/")
.post(passport.authenticate("local", { session: false }), (req, res) => {
if (req.isAuthenticated()) {
const { _id, first_name } = req.user;
const token = signToken(_id);
res.cookie("access_token", token, { httpOnly: true, sameSite: true });
res.status(200).json({
isAuthenticated: true,
user: first_name
});
}
});
module.exports = router;