🔐 Lock Down Routes with JWT Authentication

In this lesson, we'll implement a robust authentication system using JSON Web Tokens (JWT) to protect our API routes. We'll create middleware that verifies tokens and ensures only authenticated users can access protected resources.


Understanding JWT Authentication Strategy

The Bearer Token Pattern

JWT authentication in APIs typically follows the Bearer Token pattern, which is an industry-standard approach for token-based authentication:

<aside> 🔑

Bearer Token Flow

  1. Client obtains JWT after successful login
  2. Client includes token in Authorization header: Bearer <token>
  3. Server verifies token on each request
  4. Server grants or denies access based on verification </aside>

Why Bearer Tokens?

Advantage Description
Stateless No server-side session storage needed
Scalable Works across multiple servers without shared state
Standard Widely adopted HTTP authentication scheme
Secure Token never stored in cookies (no CSRF issues)

Implementation Overview

Our authentication system consists of two main components:

🛠️ JWT Utilities

src/utils/jwt.ts

🛡️ Auth Middleware

src/middleware/auth.ts


Step 1: JWT Verification Utilities

First, let's extend our JWT utilities to handle token verification:

<aside> 📝

File: src/utils/jwt.ts

</aside>

import { SignJWT, jwtVerify, decodeJwt } from 'jose'
import { createSecretKey } from 'crypto'
import env from '../env.ts'

export interface JwtPayload {
  id: string
  email: string
  username: string
}

// Existing generateToken function...

export const verifyToken = async (token: string): Promise<JwtPayload> => {
  const secretKey = createSecretKey(env.JWT_SECRET, 'utf-8')
  const { payload } = await jwtVerify(token, secretKey)

  return {
    id: [payload.id](<http://payload.id>) as string,
    email: [payload.email](<http://payload.email>) as string,
    username: payload.username as string,
  }
}


Step 2: Authentication Middleware