OAuth 2.0 & Session Management
📖 Concept
OAuth 2.0 enables "Login with Google/GitHub/Facebook" — allowing users to authenticate with third-party providers without sharing passwords. Sessions are the traditional alternative to JWTs for maintaining login state.
OAuth 2.0 Authorization Code Flow (most secure):
1. User clicks "Login with Google" → redirect to Google
2. User authorizes your app on Google
3. Google redirects back with an authorization CODE
4. Your server exchanges the CODE for an ACCESS TOKEN (server-to-server)
5. Your server uses the access token to fetch user profile from Google
6. Create/update user in your database
7. Issue your own session/JWT to the user
Sessions vs. JWTs:
| Feature | Sessions | JWT |
|---|---|---|
| State | Server-side (Redis/DB) | Client-side (token) |
| Scalability | Needs shared storage | Stateless |
| Revocation | Easy (delete session) | Hard (blacklist needed) |
| Token size | Small (session ID) | Large (up to 4KB) |
| Best for | SSR web apps | SPAs, APIs, microservices |
Passport.js is the most popular authentication middleware for Node.js. It supports 500+ strategies: Local, Google, GitHub, Facebook, Twitter, SAML, OIDC, and more.
🏠 Real-world analogy: OAuth is like a valet parking ticket. You give the valet (Google) your car (account access), they give your friend (the app) a limited-access ticket (token) that only works for parking (specific permissions), not driving (full account access).
💻 Code Example
1// OAuth 2.0 with Passport.js + Session Management23const express = require("express");4const session = require("express-session");5const passport = require("passport");6const { Strategy: GoogleStrategy } = require("passport-google-oauth20");7const { Strategy: LocalStrategy } = require("passport-local");8// const RedisStore = require("connect-redis").default;9// const redis = require("redis");1011const app = express();1213// 1. Session configuration14// const redisClient = redis.createClient({ url: process.env.REDIS_URL });15// redisClient.connect();1617app.use(18 session({19 // store: new RedisStore({ client: redisClient }), // Production: use Redis20 secret: process.env.SESSION_SECRET || "dev-secret",21 resave: false,22 saveUninitialized: false,23 cookie: {24 httpOnly: true,25 secure: process.env.NODE_ENV === "production",26 sameSite: "lax",27 maxAge: 24 * 60 * 60 * 1000, // 24 hours28 },29 })30);3132app.use(passport.initialize());33app.use(passport.session());3435// 2. Serialize/deserialize user for session36passport.serializeUser((user, done) => {37 done(null, user.id); // Store only ID in session38});3940passport.deserializeUser(async (id, done) => {41 try {42 // const user = await User.findById(id);43 const user = { id, name: "Alice", email: "alice@example.com" }; // Mock44 done(null, user);45 } catch (err) {46 done(err);47 }48});4950// 3. Local strategy (email/password)51passport.use(52 new LocalStrategy(53 { usernameField: "email" },54 async (email, password, done) => {55 try {56 // const user = await User.findByEmail(email);57 const user = { id: 1, name: "Alice", email, password: "hashed" }; // Mock5859 if (!user) return done(null, false, { message: "Invalid credentials" });6061 // const isMatch = await bcrypt.compare(password, user.password);62 const isMatch = true; // Mock63 if (!isMatch) return done(null, false, { message: "Invalid credentials" });6465 return done(null, user);66 } catch (err) {67 return done(err);68 }69 }70 )71);7273// 4. Google OAuth 2.0 strategy74passport.use(75 new GoogleStrategy(76 {77 clientID: process.env.GOOGLE_CLIENT_ID || "your-client-id",78 clientSecret: process.env.GOOGLE_CLIENT_SECRET || "your-client-secret",79 callbackURL: "/auth/google/callback",80 },81 async (accessToken, refreshToken, profile, done) => {82 try {83 // Find or create user84 // let user = await User.findOne({ googleId: profile.id });85 // if (!user) {86 // user = await User.create({87 // googleId: profile.id,88 // name: profile.displayName,89 // email: profile.emails[0].value,90 // avatar: profile.photos[0].value,91 // });92 // }93 const user = {94 id: 1,95 googleId: profile.id,96 name: profile.displayName,97 email: profile.emails?.[0]?.value,98 };99 return done(null, user);100 } catch (err) {101 return done(err);102 }103 }104 )105);106107// 5. Auth routes108app.use(express.json());109app.use(express.urlencoded({ extended: true }));110111// Local login112app.post("/auth/login", passport.authenticate("local"), (req, res) => {113 res.json({ user: req.user });114});115116// Google OAuth117app.get(118 "/auth/google",119 passport.authenticate("google", { scope: ["profile", "email"] })120);121122app.get(123 "/auth/google/callback",124 passport.authenticate("google", { failureRedirect: "/login" }),125 (req, res) => {126 res.redirect("/dashboard");127 }128);129130// Logout131app.post("/auth/logout", (req, res) => {132 req.logout((err) => {133 if (err) return res.status(500).json({ error: "Logout failed" });134 req.session.destroy();135 res.clearCookie("connect.sid");136 res.json({ message: "Logged out" });137 });138});139140// 6. Auth check middleware141function isAuthenticated(req, res, next) {142 if (req.isAuthenticated()) return next();143 res.status(401).json({ error: "Not authenticated" });144}145146// 7. Protected route147app.get("/api/profile", isAuthenticated, (req, res) => {148 res.json({ user: req.user });149});150151app.listen(3000, () => console.log("OAuth server on http://localhost:3000"));
🏋️ Practice Exercise
Exercises:
- Set up Passport.js with a Local strategy for email/password authentication
- Add Google OAuth 2.0 login — register an app on Google Cloud Console and implement the flow
- Configure sessions with Redis as the backing store for multi-server environments
- Implement "Remember Me" functionality with extended session duration
- Add CSRF protection for session-based auth using the
csurforcsrf-csrfpackage - Build a multi-provider login: support Local, Google, and GitHub authentication with linked accounts
⚠️ Common Mistakes
Storing sessions in memory — this leaks memory and doesn't work with multiple server instances; use Redis or a database
Not setting
httpOnlyandsecureflags on session cookies — without httpOnly, JavaScript can steal cookies; without secure, cookies can be interceptedConfusing OAuth access tokens with your own auth tokens — use the OAuth token only to fetch user info, then issue your own JWT/session
Not implementing CSRF protection with sessions — session cookies are sent automatically by browsers, making CSRF attacks possible
Storing the entire user object in the session — only store the user ID; fetch the full user from the database on each request
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for OAuth 2.0 & Session Management. Login to unlock this feature.