Authentication & Security

JWT, sessions, password hashing, CORS, and Helmet — protect your Express APIs

JWT Sessions bcrypt CORS Helmet

Table of Contents

1. JWT (JSON Web Token)

What it is: A compact, self-contained token that carries user identity and claims (like roles or permissions). It's digitally signed, so the server can trust its data without storing session info.

How it works:

  1. User logs in → server creates a JWT (Header + Payload + Signature) and sends it to client.
  2. Client stores it (e.g., in localStorage or cookie) and sends it in the Authorization header for each request.
  3. Server verifies signature (no database lookup needed) and extracts user info.

Short Example (Node.js / Express + jsonwebtoken):

JavaScriptconst jwt = require('jsonwebtoken'); // Login: create token const user = { id: 123, role: 'admin' }; const token = jwt.sign(user, 'your-secret-key', { expiresIn: '1h' }); // Middleware to verify token function auth(req, res, next) { const token = req.headers.authorization?.split(' ')[1]; try { const decoded = jwt.verify(token, 'your-secret-key'); req.user = decoded; next(); } catch { res.status(401).json({ error: 'Invalid token' }); } }

2. Sessions

What it is: Server-side storage for user data. After login, server creates a session record (usually in memory, Redis, or a DB) and sends a session ID (via cookie) to the client.

How it works:

  • Client sends cookie with session ID on each request.
  • Server looks up session data using that ID.
  • More secure than JWT for sensitive apps (can revoke anytime) but adds server storage overhead.

Short Example (Express + express-session):

JavaScriptconst session = require('express-session'); app.use(session({ secret: 'strong-secret', resave: false, saveUninitialized: false, cookie: { httpOnly: true, secure: true, maxAge: 3600000 } // 1 hour })); app.post('/login', (req, res) => { req.session.userId = 123; req.session.role = 'admin'; res.send('Logged in'); }); app.get('/profile', (req, res) => { if (!req.session.userId) return res.status(401).send('Unauthorized'); res.send(`User ${req.session.userId}`); });

3. Password Hashing

What it is: Storing passwords as plaintext is dangerous. Hashing transforms a password into a fixed-length, irreversible string. Salting adds random data to each password before hashing, preventing rainbow table attacks.

Good hash functions: bcrypt, Argon2, PBKDF2.

How it works (bcrypt example):

  • Signup: Generate a salt + hash password → store hash.
  • Login: Hash the incoming password (with the stored salt) and compare to stored hash.

Short Example (Node.js + bcrypt):

JavaScriptconst bcrypt = require('bcrypt'); const saltRounds = 10; // Signup const plainPassword = 'user123'; bcrypt.hash(plainPassword, saltRounds, (err, hash) => { // store hash in database }); // Login bcrypt.compare(plainPassword, storedHash, (err, result) => { if (result) console.log('Password match'); });

4. CORS (Cross-Origin Resource Sharing)

What it is: A security mechanism that controls which domains can access your API. By default, browsers block requests from different origins (domain, port, or protocol) to prevent malicious sites from reading sensitive data.

How it works:

  • Browser sends a preflight OPTIONS request (for non-simple requests) asking if the server allows the actual request.
  • Server responds with Access-Control-Allow-Origin and other headers.
  • If origin is not allowed, browser blocks the response.

Short Example (Express + cors middleware):

JavaScriptconst cors = require('cors'); // Allow only your frontend domain app.use(cors({ origin: 'https://myfrontend.com', methods: ['GET', 'POST'], allowedHeaders: ['Content-Type', 'Authorization'], credentials: true })); // Or for a simple public API app.use(cors()); // allows all origins

5. Helmet

What it is: An Express middleware that sets security-related HTTP headers to protect against common web vulnerabilities (XSS, clickjacking, MIME sniffing, etc.). It's a collection of 15+ smaller middleware functions.

Key headers set by Helmet:

  • X-Content-Type-Options: nosniff → prevents MIME type confusion.
  • X-Frame-Options: DENY → stops clickjacking via iframes.
  • Strict-Transport-Security → enforces HTTPS.
  • X-XSS-Protection → enables browser XSS filtering.

Short Example (Express + helmet):

JavaScriptconst express = require('express'); const helmet = require('helmet'); const app = express(); app.use(helmet()); // apply all defaults // Customize (optional) app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", 'trusted-cdn.com'] } } }));

Without Helmet → missing these headers, browser might guess MIME types or allow iframe embedding.

With Helmet → headers added automatically, hardening your app in one line.

Summary Table

Concept Where data lives Stateless? Main risk if ignored
JWT Client (token) Yes Token theft, no instant revoke
Sessions Server (DB/Redis) No Session hijacking
Password Hashing Server (hash only) N/A Plaintext leak → full account compromise
CORS Browser + Server N/A Unauthorized domain access
Helmet HTTP headers N/A Missing security headers