Node.js9 min read

Sessions and Cookies

Understand how sessions and cookies work. Build secure authentication systems.

Michael Torres
December 19, 2025
0.0k0

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

npm install cookie-parser
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

npm install express-session connect-redis redis
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

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

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

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

cookie: {
  secure: process.env.NODE_ENV === 'production'
}

2. Store sessions in Redis, not memory

store: new RedisStore({ client: redisClient })

3. Regenerate session ID after login

req.session.regenerate((err) => {
  req.session.userId = user._id;
});

4. Set httpOnly flag

cookie: {
  httpOnly: true
}

5. Never store passwords in sessions

req.session.userId = user._id;

Common Mistakes

Storing sensitive data in cookies

res.cookie('password', user.password);

Store only session ID

req.session.userId = user._id;

Not using HTTPS

cookie: { secure: false }

Always use HTTPS in production

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.

#Node.js#Sessions#Cookies#Authentication