diff --git a/backend/config/passportConfig.js b/backend/config/passportConfig.js index 842f50ca..34d3c37a 100644 --- a/backend/config/passportConfig.js +++ b/backend/config/passportConfig.js @@ -35,9 +35,12 @@ passport.serializeUser((user, done) => { }); // Deserialize user (retrieve user from session) +// Select only the fields needed for request handling. Excluding password +// prevents the bcrypt hash from being attached to req.user and accidentally +// serialized into API responses. passport.deserializeUser(async (id, done) => { try { - const user = await User.findById(id); + const user = await User.findById(id).select('-password -__v').lean(); done(null, user); } catch (err) { done(err, null); diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 7c2cda78..467313a6 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -36,14 +36,23 @@ router.post("/login", validateRequest(loginSchema), passport.authenticate('local }); // Logout route -router.get("/logout", (req, res) => { +// POST is required here: GET is a safe/idempotent method that browsers trigger +// automatically (img src, prefetch, etc.), which would allow any third-party +// page to force-logout authenticated users via a passive CSRF request. +router.post("/logout", (req, res) => { req.logout((err) => { if (err) return res.status(500).json({ message: 'Logout failed', error: err.message }); - else + + req.session.destroy((destroyErr) => { + if (destroyErr) { + return res.status(500).json({ message: 'Session cleanup failed', error: destroyErr.message }); + } + res.clearCookie('connect.sid'); res.status(200).json({ message: 'Logged out successfully' }); + }); }); }); diff --git a/backend/server.js b/backend/server.js index 48d6ccfb..80ad6d38 100644 --- a/backend/server.js +++ b/backend/server.js @@ -28,10 +28,18 @@ app.use(cors({ // Middleware app.use(bodyParser.json()); +const isProduction = process.env.NODE_ENV === 'production'; + app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, + cookie: { + httpOnly: true, + secure: isProduction, + sameSite: 'strict', + maxAge: 24 * 60 * 60 * 1000, // 24 hours + }, })); app.use(passport.initialize()); app.use(passport.session());