Skip to content

hoangsonww/JWT-Module

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

🔒 jwt-module

A standalone JWT authentication module built with Express and TypeScript.

TypeScript Node.js Express JWT Zod bcrypt Helmet Jest Docker Azure Pipelines License


Overview

jwt-module is a self-contained authentication service providing user registration, login, JWT-based access/refresh tokens with rotation, and full account management through a REST API. It uses in-memory storage, making it ideal for development, prototyping, and learning.

graph LR
    Client([Client]) -->|HTTP| API[Express API Layer]
    API -->|AuthService interface| Auth[Auth Service Core]
    Auth -->|read/write| Store[(In-Memory Store)]
Loading

Important

Note: All data is lost on restart. For production use, implement a persistent backing store behind the existing interfaces.


Features

  • 👤 User Registration -- email/password signup with duplicate detection
  • 🔑 Login -- credential verification with JWT token pair issuance
  • 🔄 Refresh Token Rotation -- old tokens revoked on every refresh, new pair issued
  • 🛡️ Account Lockout -- automatic lock after 5 failed login attempts (15 min cooldown)
  • 🚥 Rate Limiting -- per-IP sliding window (20 requests / 15 min) on sensitive endpoints
  • 📤 Logout / Logout All -- single-session and all-session token revocation
  • 🔒 Change Password -- requires current password, revokes all sessions
  • ✉️ Update Email -- requires password confirmation, duplicate check
  • 🗑️ Delete Account -- requires password confirmation, full cleanup
  • 🔍 Get Profile -- returns user ID, email, and creation date
  • Health Check -- GET /health for uptime monitoring
  • 🛡️ Helmet -- security headers on all responses
  • 🌐 CORS -- configurable allowed origins
  • 📝 Zod Validation -- schema validation on all request bodies

Quick Start

# Clone the repository
git clone https://github.com/hoangsonww/JWT-Module.git
cd JWT-Module

# Install dependencies
npm install

# Set environment variables (or use dev defaults)
cp .env.example .env

# Build
npm run build

# Start development server
npx ts-node src/server.ts

The server starts on http://localhost:3000 by default. A test UI is served at the root URL.


Environment Variables

Variable Description Default Required
JWT_ACCESS_SECRET Secret for signing access tokens Dev fallback Yes (in production)
JWT_REFRESH_SECRET Secret for signing refresh tokens Dev fallback Yes (in production)
PORT Server listen port 3000 No
NODE_ENV Runtime environment -- No
CORS_ORIGIN Allowed origins (comma-separated or *) * No

In development, fallback secrets are applied automatically. Always set real secrets in production.


API Reference

All endpoints return JSON. Error responses use the shape:

{ "error": { "code": "ERROR_CODE", "message": "Human-readable message" } }

POST /auth/register

Create a new account.

  • Auth: None
  • Body: { "email": "user@example.com", "password": "securePass1" }
  • Success: 201
    { "tokens": { "accessToken": "...", "refreshToken": "..." } }
  • Errors: 400 INVALID_EMAIL | 400 WEAK_PASSWORD | 400 INVALID_INPUT | 409 DUPLICATE_EMAIL

POST /auth/login

Authenticate and receive tokens.

  • Auth: None
  • Body: { "email": "user@example.com", "password": "securePass1" }
  • Success: 200
    { "tokens": { "accessToken": "...", "refreshToken": "..." } }
  • Errors: 401 INVALID_CREDENTIALS | 423 ACCOUNT_LOCKED | 400 INVALID_INPUT

POST /auth/refresh

Exchange a refresh token for a new token pair. The old refresh token is revoked (rotation).

  • Auth: None
  • Body: { "refreshToken": "..." }
  • Success: 200
    { "tokens": { "accessToken": "...", "refreshToken": "..." } }
  • Errors: 401 INVALID_TOKEN | 401 TOKEN_EXPIRED | 404 USER_NOT_FOUND | 400 INVALID_INPUT

POST /auth/logout

Revoke a single refresh token.

  • Auth: None
  • Body: { "refreshToken": "..." }
  • Success: 200
    { "message": "Logged out successfully" }

POST /auth/logout-all

Revoke all refresh tokens for the authenticated user.

  • Auth: Bearer token
  • Body: None
  • Success: 200
    { "message": "All sessions revoked successfully" }
  • Errors: 401 MISSING_TOKEN | 401 INVALID_TOKEN | 401 TOKEN_EXPIRED

POST /auth/change-password

Change password for the authenticated user. Revokes all sessions.

  • Auth: Bearer token
  • Body: { "currentPassword": "oldPass1", "newPassword": "newPass1" }
  • Success: 200
    { "message": "Password changed successfully" }
  • Errors: 401 MISSING_TOKEN | 401 INVALID_CREDENTIALS | 400 WEAK_PASSWORD | 400 INVALID_INPUT

GET /auth/me

Get the authenticated user's profile.

  • Auth: Bearer token
  • Success: 200
    { "id": "...", "email": "user@example.com", "createdAt": "2026-01-01T00:00:00.000Z" }
  • Errors: 401 MISSING_TOKEN | 401 INVALID_TOKEN | 404 USER_NOT_FOUND

PATCH /auth/me

Update the authenticated user's email.

  • Auth: Bearer token
  • Body: { "newEmail": "new@example.com", "password": "currentPass1" }
  • Success: 200
    { "message": "Email updated successfully" }
  • Errors: 401 MISSING_TOKEN | 401 INVALID_CREDENTIALS | 400 INVALID_EMAIL | 409 DUPLICATE_EMAIL | 400 INVALID_INPUT

DELETE /auth/me

Delete the authenticated user's account.

  • Auth: Bearer token
  • Body: { "password": "currentPass1" }
  • Success: 200
    { "message": "Account deleted successfully" }
  • Errors: 401 MISSING_TOKEN | 401 INVALID_CREDENTIALS | 400 INVALID_INPUT

GET /health

Health check endpoint.

  • Auth: None
  • Success: 200
    { "status": "ok" }

Authentication Flow

sequenceDiagram
    participant C as Client
    participant A as API
    participant S as Auth Service

    C->>A: POST /auth/register {email, password}
    A->>S: register(input)
    S-->>A: {accessToken, refreshToken}
    A-->>C: 201 {tokens}

    C->>A: POST /auth/login {email, password}
    A->>S: login(input)
    S-->>A: {accessToken, refreshToken}
    A-->>C: 200 {tokens}

    C->>A: GET /auth/me [Bearer accessToken]
    A->>A: authenticateToken middleware
    A->>S: getUserById(userId)
    S-->>A: user
    A-->>C: 200 {id, email, createdAt}

    Note over C,S: Access token expires after 15 min

    C->>A: POST /auth/refresh {refreshToken}
    A->>S: refreshTokens(refreshToken)
    S->>S: Revoke old token, issue new pair
    S-->>A: {newAccessToken, newRefreshToken}
    A-->>C: 200 {tokens}

    C->>A: POST /auth/logout {refreshToken}
    A->>S: logout(refreshToken)
    S->>S: Revoke token
    A-->>C: 200 {message}
Loading

Security Features

Request Lifecycle

Every incoming request passes through multiple security layers before reaching the auth service:

flowchart TD
    REQ([Incoming Request]) --> HELMET[Helmet Security Headers]
    HELMET --> CORS[CORS Check]
    CORS --> LOGGER[Request Logger]
    LOGGER --> BODY[Body Parser - 10kb limit]
    BODY --> ROUTE{Route Match}

    ROUTE -->|Public| RATE[Rate Limiter]
    ROUTE -->|Protected| AUTH[Bearer Token Check]

    AUTH -->|Valid| RATE2[Rate Limiter]
    AUTH -->|Invalid| R401([401 Unauthorized])

    RATE --> ZOD[Zod Schema Validation]
    RATE2 --> ZOD
    RATE -->|Exceeded| R429([429 Too Many Requests])
    RATE2 -->|Exceeded| R429

    ZOD -->|Valid| SERVICE[Auth Service]
    ZOD -->|Invalid| R400([400 Invalid Input])

    SERVICE -->|Lockout check| LOCK{Account Locked?}
    LOCK -->|No| PROCESS[Process Request]
    LOCK -->|Yes| R423([423 Locked])

    PROCESS --> RESP([Success Response])
Loading

Security Summary

Layer Mechanism Details
Transport Helmet Security headers (X-Content-Type-Options, X-Frame-Options, etc.)
Transport CORS Configurable allowed origins
Transport Body size limit 10kb max JSON payload
Rate control Per-IP rate limiter 20 requests per 15-minute sliding window
Input validation Zod schemas Type-safe validation on all request bodies
Authentication JWT HS256 Algorithm pinning prevents substitution attacks
Authorization Bearer middleware Access token verification on protected routes
Brute force Account lockout 5 failed attempts triggers 15-minute lock
Token security Refresh rotation Old tokens revoked on each refresh
Password bcrypt (12 rounds) Adaptive hashing with salt

Account Lockout Flow

stateDiagram-v2
    [*] --> Normal

    Normal --> FailedAttempt: Wrong password
    FailedAttempt --> FailedAttempt: Wrong password (count < 5)
    FailedAttempt --> Locked: 5th failed attempt
    FailedAttempt --> Normal: Successful login (resets count)

    Locked --> Normal: 15 minutes elapsed
    Locked --> Locked: Login attempt (rejected)
Loading
  • Threshold: 5 consecutive failed attempts per email
  • Lockout duration: 15 minutes
  • Reset: Successful login clears the failure counter

Error Codes

Error Code HTTP Status Description
INVALID_INPUT 400 Request body failed Zod validation
INVALID_EMAIL 400 Email format is invalid
WEAK_PASSWORD 400 Password does not meet strength requirements
INVALID_CREDENTIALS 401 Wrong email or password
MISSING_TOKEN 401 Authorization header missing or malformed
INVALID_TOKEN 401 Token is invalid or revoked
TOKEN_EXPIRED 401 Token has expired
USER_NOT_FOUND 404 User does not exist
DUPLICATE_EMAIL 409 Email already registered
ACCOUNT_LOCKED 423 Too many failed login attempts
RATE_LIMITED 429 IP exceeded request limit
MISSING_SECRET 500 JWT secret environment variable not set
INTERNAL_ERROR 500 Unexpected server error

Password Requirements

  • Minimum 8 characters
  • At least one letter (a-z or A-Z)
  • At least one digit (0-9)
  • Hashed with bcrypt using 12 salt rounds

Demo UI


Development

Commands

# Run tests
npm test

# Run tests with coverage
npm run test:coverage

# Build TypeScript
npm run build

# Start dev server (with default secrets)
npx ts-node src/server.ts

# Start on custom port
PORT=5001 npx ts-node src/server.ts

Project Structure

src/
  auth/                  # Core auth logic (no HTTP dependency)
    auth-service.ts      # Registration, login, refresh, account management
    errors.ts            # AuthError class and AuthErrorCode union type
    password.ts          # bcrypt hashing and password strength validation
    token.ts             # JWT generation, verification, revocation blacklist
    types.ts             # Shared interfaces (User, AuthTokens, TokenPayload, etc.)
    index.ts             # Barrel export for auth module
  api/                   # HTTP layer
    app.ts               # Express app factory, AuthService interface
    auth-router.ts       # Route handlers, error-to-HTTP-status mapping
    middleware.ts         # authenticateToken, requestLogger
    rate-limiter.ts      # Per-IP sliding window rate limiter
    validation.ts        # Zod schemas for all request bodies
  server.ts              # Entry point -- wires auth-service into Express app
public/                  # Static test UI

Adding a New Endpoint

  1. Add the method to the AuthService interface in src/api/app.ts
  2. Implement the logic in src/auth/auth-service.ts
  3. Add a Zod schema in src/api/validation.ts
  4. Add the route handler in src/api/auth-router.ts
  5. If a new error code is needed, add it to AuthErrorCode in src/auth/errors.ts and to ERROR_STATUS_MAP in src/api/auth-router.ts

Architecture

See ARCHITECTURE.md for detailed architecture documentation with diagrams.


License

MIT

About

A standalone JWT authentication service built with Express and TypeScript, providing the full auth lifecycle including registration, login, access/refresh token rotation, account management, and security features like rate limiting, account lockout, and bcrypt password hashing.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors