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

codeTap to expand ⛶
1// OAuth 2.0 with Passport.js + Session Management
2
3const 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");
10
11const app = express();
12
13// 1. Session configuration
14// const redisClient = redis.createClient({ url: process.env.REDIS_URL });
15// redisClient.connect();
16
17app.use(
18 session({
19 // store: new RedisStore({ client: redisClient }), // Production: use Redis
20 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 hours
28 },
29 })
30);
31
32app.use(passport.initialize());
33app.use(passport.session());
34
35// 2. Serialize/deserialize user for session
36passport.serializeUser((user, done) => {
37 done(null, user.id); // Store only ID in session
38});
39
40passport.deserializeUser(async (id, done) => {
41 try {
42 // const user = await User.findById(id);
43 const user = { id, name: "Alice", email: "alice@example.com" }; // Mock
44 done(null, user);
45 } catch (err) {
46 done(err);
47 }
48});
49
50// 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" }; // Mock
58
59 if (!user) return done(null, false, { message: "Invalid credentials" });
60
61 // const isMatch = await bcrypt.compare(password, user.password);
62 const isMatch = true; // Mock
63 if (!isMatch) return done(null, false, { message: "Invalid credentials" });
64
65 return done(null, user);
66 } catch (err) {
67 return done(err);
68 }
69 }
70 )
71);
72
73// 4. Google OAuth 2.0 strategy
74passport.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 user
84 // 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);
106
107// 5. Auth routes
108app.use(express.json());
109app.use(express.urlencoded({ extended: true }));
110
111// Local login
112app.post("/auth/login", passport.authenticate("local"), (req, res) => {
113 res.json({ user: req.user });
114});
115
116// Google OAuth
117app.get(
118 "/auth/google",
119 passport.authenticate("google", { scope: ["profile", "email"] })
120);
121
122app.get(
123 "/auth/google/callback",
124 passport.authenticate("google", { failureRedirect: "/login" }),
125 (req, res) => {
126 res.redirect("/dashboard");
127 }
128);
129
130// Logout
131app.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});
139
140// 6. Auth check middleware
141function isAuthenticated(req, res, next) {
142 if (req.isAuthenticated()) return next();
143 res.status(401).json({ error: "Not authenticated" });
144}
145
146// 7. Protected route
147app.get("/api/profile", isAuthenticated, (req, res) => {
148 res.json({ user: req.user });
149});
150
151app.listen(3000, () => console.log("OAuth server on http://localhost:3000"));

🏋️ Practice Exercise

Exercises:

  1. Set up Passport.js with a Local strategy for email/password authentication
  2. Add Google OAuth 2.0 login — register an app on Google Cloud Console and implement the flow
  3. Configure sessions with Redis as the backing store for multi-server environments
  4. Implement "Remember Me" functionality with extended session duration
  5. Add CSRF protection for session-based auth using the csurf or csrf-csrf package
  6. 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 httpOnly and secure flags on session cookies — without httpOnly, JavaScript can steal cookies; without secure, cookies can be intercepted

  • Confusing 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.