Sessions and Cookies
Understand how sessions and cookies work. Build secure authentication systems.
Sessions and Cookies
The Problem: HTTP is Stateless
HTTP doesn't remember who you are. Every request is independent.
``` Request 1: Login as John Request 2: View profile → Server doesn't know you're John! Request 3: Add to cart → Server doesn't know you're John! ```
We need a way to track users across requests. Two solutions: **Cookies** and **Sessions**.
What are Cookies?
Cookies are small pieces of data stored in the browser. Server sends cookies, browser sends them back with every request.
``` Browser → Request → Server Server → Response + Set-Cookie: username=john Browser stores: username=john
Next request: Browser → Request + Cookie: username=john → Server Server knows: This is John! ```
**Cookies live in the browser.** Anyone can see and modify them.
What are Sessions?
Sessions store data on the server. Only a session ID is stored in a cookie.
``` Browser → Login → Server Server creates session: sessionId: abc123 userId: 42 email: john@example.com
Server → Response + Set-Cookie: sessionId=abc123 Browser stores: sessionId=abc123
Next request: Browser → Request + Cookie: sessionId=abc123 → Server Server looks up session abc123 → Finds userId: 42 Server knows: This is user 42 (John)! ```
**Session data lives on server.** More secure than cookies.
Cookies vs Sessions
``` Cookies: ✅ Simple to implement ✅ No server storage needed ❌ Limited size (4KB) ❌ Visible to user ❌ Can be modified by user ❌ Sent with every request (bandwidth)
Sessions: ✅ Unlimited data size ✅ Data hidden from user ✅ More secure ✅ Only session ID sent ❌ Requires server storage ❌ More complex setup ```
Basic Cookies
```bash npm install cookie-parser ```
```javascript const express = require('express'); const cookieParser = require('cookie-parser');
const app = express(); app.use(cookieParser());
app.get('/set-cookie', (req, res) => { res.cookie('username', 'john', { maxAge: 900000, httpOnly: true, secure: true, sameSite: 'strict' }); res.send('Cookie set'); });
app.get('/get-cookie', (req, res) => { const username = req.cookies.username; res.send(`Username: ${username}`); });
app.listen(3000); ```
**Cookie Options Explained:** - `maxAge`: How long cookie lives (milliseconds) - `httpOnly`: JavaScript can't access it (prevents XSS attacks) - `secure`: Only sent over HTTPS - `sameSite`: Prevents CSRF attacks
Sessions with express-session
```bash npm install express-session connect-redis redis ```
```javascript const session = require('express-session'); const RedisStore = require('connect-redis').default; const { createClient } = require('redis');
const redisClient = createClient(); redisClient.connect();
app.use(session({ store: new RedisStore({ client: redisClient }), secret: 'your-secret-key', resave: false, saveUninitialized: false, cookie: { secure: true, httpOnly: true, maxAge: 1000 * 60 * 60 * 24 } })); ```
**Why Redis?** In-memory storage is fast. Sessions need quick access.
**Session Options:** - `secret`: Signs session ID (prevents tampering) - `resave`: Don't save if nothing changed - `saveUninitialized`: Don't save empty sessions
Complete Login System
```javascript const bcrypt = require('bcrypt');
app.post('/login', async (req, res) => { const { email, password } = req.body; const user = await User.findOne({ email }); if (!user) { return res.status(401).json({ error: 'Invalid credentials' }); } const isValid = await bcrypt.compare(password, user.password); if (!isValid) { return res.status(401).json({ error: 'Invalid credentials' }); } req.session.regenerate((err) => { if (err) return res.status(500).json({ error: 'Session error' }); req.session.userId = user._id; req.session.email = user.email; req.session.role = user.role; res.json({ message: 'Logged in successfully', user: { id: user._id, email: user.email, name: user.name } }); }); });
app.get('/dashboard', (req, res) => { if (!req.session.userId) { return res.status(401).json({ error: 'Not authenticated' }); } res.json({ message: 'Welcome to dashboard', userId: req.session.userId, email: req.session.email }); });
app.post('/logout', (req, res) => { req.session.destroy((err) => { if (err) return res.status(500).json({ error: 'Logout failed' }); res.clearCookie('connect.sid'); res.json({ message: 'Logged out successfully' }); }); }); ```
**Why `regenerate()`?** Creates new session ID after login. Prevents session fixation attacks.
Auth Middleware
```javascript function requireAuth(req, res, next) { if (!req.session.userId) { return res.status(401).json({ error: 'Authentication required' }); } next(); }
function requireAdmin(req, res, next) { if (!req.session.userId) { return res.status(401).json({ error: 'Authentication required' }); } if (req.session.role !== 'admin') { return res.status(403).json({ error: 'Admin access required' }); } next(); }
app.get('/profile', requireAuth, async (req, res) => { const user = await User.findById(req.session.userId); res.json(user); });
app.get('/admin/users', requireAdmin, async (req, res) => { const users = await User.find(); res.json(users); }); ```
Remember Me Feature
```javascript app.post('/login', async (req, res) => { const { email, password, rememberMe } = req.body; if (rememberMe) { req.session.cookie.maxAge = 1000 * 60 * 60 * 24 * 30; } else { req.session.cookie.maxAge = 1000 * 60 * 60 * 24; } res.json({ message: 'Logged in' }); }); ```
**Without Remember Me:** Session expires in 24 hours **With Remember Me:** Session expires in 30 days
Best Practices
**1. Always use HTTPS in production** ```javascript cookie: { secure: process.env.NODE_ENV === 'production' } ```
**2. Store sessions in Redis, not memory** ```javascript store: new RedisStore({ client: redisClient }) ```
**3. Regenerate session ID after login** ```javascript req.session.regenerate((err) => { req.session.userId = user._id; }); ```
**4. Set httpOnly flag** ```javascript cookie: { httpOnly: true } ```
**5. Never store passwords in sessions** ```javascript req.session.userId = user._id; ```
Common Mistakes
❌ **Storing sensitive data in cookies** ```javascript res.cookie('password', user.password); ```
✅ **Store only session ID** ```javascript req.session.userId = user._id; ```
❌ **Not using HTTPS** ```javascript cookie: { secure: false } ```
✅ **Always use HTTPS in production** ```javascript cookie: { secure: true } ```
Key Takeaway
Sessions store data on server (secure). Cookies store data in browser (less secure). Use express-session with Redis for production. Always regenerate session ID after login. Set httpOnly and secure flags. Sessions are perfect for authentication because data stays on server where users can't tamper with it.