Unit 4.2 · Tutorial 1

Authentication Basics (JWT & Sessions)

Chapter 11 · Backend Development
~3 hours Intermediate JWT · bcrypt · Sessions · Cookies

Overview

Authentication is the cornerstone of secure applications. This tutorial covers the two primary authentication methods: JWT (stateless) and sessions (stateful). You'll learn to hash passwords with bcrypt, generate and verify JWTs, and manage user sessions with cookies.

Why this matters: Protecting user data is critical. Understanding authentication ensures your applications are secure and user identities are verified.

1. Authentication vs Authorization

  • Authentication: Verifying who a user is (login).
  • Authorization: Determining what a user can do (permissions).
  • Common flows: Login → Validate credentials → Return token/session → Use token/session for subsequent requests.
// Authentication flow // 1. User submits credentials (email + password) // 2. Server validates credentials // 3. Server creates a session or JWT // 4. Client includes session cookie or JWT in requests // 5. Server validates the session or JWT on each request

2. Password Hashing with bcrypt

Never store passwords in plain text. Use bcrypt to hash passwords securely.

npm install bcrypt
const bcrypt = require('bcrypt'); const saltRounds = 10; // Hash a password async function hashPassword(plainPassword) { const hash = await bcrypt.hash(plainPassword, saltRounds); return hash; } // Compare a password with a hash async function comparePassword(plainPassword, hashedPassword) { const match = await bcrypt.compare(plainPassword, hashedPassword); return match; // true if match, false otherwise } // Example usage in registration const hashed = await hashPassword('userpassword123'); // Store hashed in database // Example usage in login const isValid = await comparePassword('userpassword123', storedHash); if (isValid) { // Password matches, generate token/session }

3. JSON Web Tokens (JWT)

JWT is a compact, URL‑safe token format used for stateless authentication.

npm install jsonwebtoken
const jwt = require('jsonwebtoken'); const JWT_SECRET = process.env.JWT_SECRET || 'my-secret-key'; // Generate a token function generateToken(userId, email) { const payload = { userId, email }; const options = { expiresIn: '7d' }; return jwt.sign(payload, JWT_SECRET, options); } // Verify a token function verifyToken(token) { try { const decoded = jwt.verify(token, JWT_SECRET); return decoded; } catch (error) { return null; // invalid token } } // Login endpoint app.post('/api/auth/login', async (req, res) => { const { email, password } = req.body; const user = await findUserByEmail(email); if (!user) { return res.status(401).json({ error: 'Invalid credentials' }); } const isValid = await comparePassword(password, user.passwordHash); if (!isValid) { return res.status(401).json({ error: 'Invalid credentials' }); } const token = generateToken(user.id, user.email); res.json({ token, user: { id: user.id, email: user.email } }); }); // Authentication middleware function authenticate(req, res, next) { const authHeader = req.headers.authorization; if (!authHeader) { return res.status(401).json({ error: 'No token provided' }); } const token = authHeader.split(' ')[1]; // Bearer const decoded = verifyToken(token); if (!decoded) { return res.status(401).json({ error: 'Invalid token' }); } req.user = decoded; next(); } // Protected route app.get('/api/profile', authenticate, (req, res) => { res.json({ user: req.user }); });

4. Session Management

Sessions store user data on the server and use a cookie to identify the client.

npm install express-session connect-redis redis
const session = require('express-session'); const RedisStore = require('connect-redis')(session); const redisClient = require('redis').createClient(); app.use(session({ store: new RedisStore({ client: redisClient }), secret: process.env.SESSION_SECRET || 'session-secret', resave: false, saveUninitialized: false, cookie: { secure: process.env.NODE_ENV === 'production', httpOnly: true, maxAge: 1000 * 60 * 60 * 24, // 24 hours }, })); // Login endpoint (session-based) app.post('/api/auth/login', async (req, res) => { const { email, password } = req.body; const user = await findUserByEmail(email); if (!user || !(await comparePassword(password, user.passwordHash))) { return res.status(401).json({ error: 'Invalid credentials' }); } // Store user in session req.session.userId = user.id; req.session.email = user.email; res.json({ message: 'Logged in successfully', user: { id: user.id, email: user.email } }); }); // Protected route (session-based) app.get('/api/profile', (req, res) => { if (!req.session.userId) { return res.status(401).json({ error: 'Unauthorized' }); } res.json({ user: { id: req.session.userId, email: req.session.email } }); }); // Logout app.post('/api/auth/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' }); }); });

5. JWT vs Sessions

// Comparison | Feature | JWT | Sessions | |---------|-----|----------| | Storage | Client (stateless) | Server (stateful) | | Scalability | Easy (no server state) | Requires shared session store (Redis) | | Invalidation | Hard (until expiry) | Easy (destroy session) | | Size | Larger (contains data) | Small (only session ID) | | Use Case | Microservices, SPAs | Traditional web apps |
  • When to use JWT: Distributed systems, mobile apps, microservices, stateless APIs.
  • When to use sessions: Traditional web apps, server‑side rendering, easy invalidation needed.
Best practice: For APIs, JWT is the most common choice. For full‑stack web apps with server‑side rendering, sessions are often simpler.

Quiz

Question 1

What is the purpose of hashing passwords with bcrypt?

  • To encrypt the password for transmission
  • To store passwords securely without storing them in plain text
  • To compress the password
  • To generate a random token
Show answer
B. To store passwords securely without storing them in plain text.

Question 2

Which part of a JWT contains the user data?

  • Header
  • Payload
  • Signature
  • All of the above
Show answer
B. Payload.

Question 3

What is the main disadvantage of session-based authentication compared to JWT?

  • It's less secure
  • It requires server-side storage, making scaling harder
  • It's faster
  • It uses more bandwidth
Show answer
B. It requires server-side storage, making scaling harder.

Exercises

Exercise 1

Implement a user registration endpoint that hashes the password with bcrypt and stores it in an in‑memory array (or database).

Sample answer
app.post('/api/register', async (req, res) => { const { email, password, name } = req.body; const hashed = await bcrypt.hash(password, 10); const user = { id: users.length + 1, email, name, passwordHash: hashed }; users.push(user); res.status(201).json({ id: user.id, email: user.email, name: user.name }); });

Exercise 2

Add a login endpoint that verifies credentials and returns a JWT token.

Sample answer
app.post('/api/login', async (req, res) => { const { email, password } = req.body; const user = users.find(u => u.email === email); if (!user) return res.status(401).json({ error: 'Invalid credentials' }); const valid = await bcrypt.compare(password, user.passwordHash); if (!valid) return res.status(401).json({ error: 'Invalid credentials' }); const token = jwt.sign({ userId: user.id, email: user.email }, JWT_SECRET); res.json({ token }); });

Homework

Homework 1

Extend the Task Manager API with JWT authentication. Add registration and login endpoints. Protect the task CRUD endpoints so that only authenticated users can access them. Use the user ID from the JWT to associate tasks with users.

Sample outline
  • Register: POST /api/register – stores user with hashed password.
  • Login: POST /api/login – returns JWT.
  • Middleware: authenticate(req, res, next) – verifies JWT and attaches user.
  • Routes: All task routes use authenticate middleware.
  • Data: Each task has a `userId` field.

Mini‑Project

Authenticated Task Manager

Build a complete authenticated Task Manager API:

  • User registration and login with JWT
  • Tasks are associated with users
  • Protected endpoints for creating, reading, updating, and deleting tasks
  • User can only access their own tasks
  • Use bcrypt for password hashing
  • Add a logout endpoint (client-side token discard)
Sample outline
  • Users: id, email, name, passwordHash.
  • Tasks: id, title, description, completed, userId.
  • Auth: bcrypt for hashing, JWT for tokens.
  • Middleware: authenticate extracts user from token.
  • Routes: GET /tasks, POST /tasks, PUT /tasks/:id, DELETE /tasks/:id (all protected).

Tutorial Summary

You learned authentication fundamentals: password hashing with bcrypt, JWT generation and verification, and session management. You also compared JWT and sessions to understand when to use each approach.

Key takeaway: Authentication is critical for security. Always hash passwords, use secure tokens, and choose the right strategy for your application.