Authentication Basics

Sessions vs JWTs — and the Patterns That Actually Work

Authentication Basics

Hash passwords, issue tokens, protect routes. Know when to use sessions, when to use JWTs, and when to outsource the whole thing.

5 min read Level 3/5 #nodejs#auth#jwt
What you'll learn
  • Hash passwords correctly
  • Implement a simple JWT flow
  • Choose between sessions and JWTs

Auth has many shapes — and many sharp edges. Pick a pattern, use a library.

Step 1 — Hash Passwords

Never store plaintext. Use a slow, salted KDF:

npm install bcrypt
import bcrypt from "bcrypt";

const hash = await bcrypt.hash("user-password", 12);
// store `hash` in DB

const ok = await bcrypt.compare("user-password", hash);
// ok === true

12 is the cost factor — higher = slower = more secure. Tune so hashing takes ~100ms on your hardware.

argon2 is a slightly more modern alternative — both fine.

Step 2 — Issue a Token on Login

After verifying the password, give the client a token they’ll send on every request.

Option A: JWT (Stateless)

npm install jsonwebtoken
import jwt from "jsonwebtoken";

const SECRET = process.env.JWT_SECRET;

function issueToken(user) {
  return jwt.sign({ sub: user.id, email: user.email }, SECRET, { expiresIn: "1h" });
}

function verifyToken(token) {
  return jwt.verify(token, SECRET);
}

Pros: stateless, scales horizontally without shared session storage. Cons: can’t be revoked easily, mistakes have big blast radius (stolen tokens valid until expiry).

Option B: Sessions (Stateful)

import express from "express";
import session from "express-session";
import RedisStore from "connect-redis";

app.use(session({
  store: new RedisStore({ client: redis }),
  secret: process.env.SESSION_SECRET,
  cookie: { httpOnly: true, secure: true, sameSite: "lax", maxAge: 24 * 3600 * 1000 },
}));

// after login:
req.session.userId = user.id;

Pros: revocation is trivial (delete the session), simpler mental model. Cons: needs a session store (Redis, DB).

Step 3 — Protect Routes

function requireAuth(req, res, next) {
  const token = req.headers.authorization?.replace("Bearer ", "");
  if (!token) return res.status(401).json({ error: "missing token" });
  try {
    req.user = verifyToken(token);
    next();
  } catch {
    res.status(401).json({ error: "invalid token" });
  }
}

app.get("/api/me", requireAuth, (req, res) => res.json(req.user));

Just Outsource It

For real apps, consider:

  • Auth0, Clerk, Supabase Auth, Auth.js (NextAuth)
  • They handle: OAuth (Google/GitHub), MFA, session rotation, email verification, password resets
  • You handle: less

Rolling your own auth is a project that never ends well. Use a service unless you have very specific needs.

Rate Limiting →