Skip to content

AnishBhandarkar/jwt-auth-api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Authentication System - Node.js/Express + MongoDB

A production-grade authentication system implementing JWT access tokens and HTTP-only refresh token cookies with MongoDB storage.

πŸ“‹ Table of Contents

✨ Features

  • JWT Access Tokens - Short-lived (15 min) tokens sent in response body
  • HTTP-Only Refresh Tokens - Long-lived (7 days) tokens stored in secure cookies
  • MongoDB Storage - Refresh tokens hashed and stored with user reference
  • Password Hashing - bcrypt for secure password storage
  • Token Expiry Management - Automatic cleanup using MongoDB TTL indexes
  • Security Headers - Helmet.js for HTTP security headers
  • Rate Limiting - Prevent brute force attacks
  • Session Management - Multiple device sessions supported
  • Logout Functionality - Token revocation on logout

πŸ› οΈ Tech Stack

Backend

  • Node.js - Runtime environment
  • Express.js - Web framework
  • MongoDB - Database
  • Mongoose - ODM for MongoDB

Key Packages

{
  "dependencies": {
    "express": "^4.18.2",
    "mongoose": "^7.5.0",
    "bcrypt": "^5.1.1",
    "jsonwebtoken": "^9.0.2",
    "cookie-parser": "^1.4.6",
    "dotenv": "^16.3.1",
    "helmet": "^7.0.0",
    "express-rate-limit": "^6.10.0"
  }
}

πŸ”„ System Flow Diagram

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Client    │────▢│  /signup     │────▢│   MongoDB   β”‚
β”‚   (Frontend)β”‚     β”‚  /login      β”‚     β”‚  - Users    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚  - Refresh  β”‚
        β–²                  β”‚              β”‚    Tokens   β”‚
        β”‚                  β”‚              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚           β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”              β”‚
        β”‚           β”‚ Generate    β”‚              β”‚
        β”‚           β”‚ Tokens:     β”‚β—€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚           β”‚ β€’ Access    β”‚
        β”‚           β”‚ β€’ Refresh   β”‚
        β”‚           β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
        β”‚                  β”‚
        β”‚           β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
        └───────────│ Response:   β”‚
                    β”‚ β€’ Access in β”‚
                    β”‚   Body      β”‚
                    β”‚ β€’ Refresh inβ”‚
                    β”‚   Cookie    β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Request   │────▢│ Authenticate │────▢│   Return    β”‚
β”‚   /profile  β”‚     β”‚  Middleware  β”‚     β”‚  User Data  β”‚
β”‚  + Access   β”‚     β”‚ β€’ Verify JWT β”‚     β”‚             β”‚
β”‚    Token    β”‚     β”‚ β€’ Check exp  β”‚     β”‚             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Access Token│────▢│   /refresh    │────▢│  Verify     β”‚
β”‚   Expired   β”‚     β”‚  + Cookie     β”‚     β”‚  Refresh    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚  (auto-sent)  β”‚     β”‚  Token in   β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚  Database   β”‚
                            β”‚             β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
                            β”‚                    β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
                    β”‚ Generate New  β”‚    β”‚   Check     β”‚
                    β”‚ Access Token  │◀───│  Expiry &   β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚   User      β”‚
                            β”‚            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  Return New   β”‚
                    β”‚  Access Token β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   /logout   │────▢│ Delete Token │────▢│ Clear       β”‚
β”‚  + Cookie   β”‚     β”‚  from DB     β”‚     β”‚ Cookie      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“‘ API Endpoints

1. Signup - POST /api/auth/signup

Request Body:

{
  "firstName": "John",
  "lastName": "Doe",
  "emailId": "john@example.com",
  "password": "password123"
}

Response:

{
  "message": "User created successfully",
  "accessToken": "eyJhbGc...",
  "user": {
    "id": "650f1a2b3c4d5e6f7a8b9c0d",
    "firstName": "John",
    "lastName": "Doe",
    "email": "john@example.com"
  }
}

Sets Cookie: refreshToken (HTTP-only, 7 days)

2. Login - POST /api/auth/login

Request Body:

{
  "emailId": "john@example.com",
  "password": "password123"
}

Response:

{
  "message": "Login successful",
  "accessToken": "eyJhbGc...",
  "user": {
    "id": "650f1a2b3c4d5e6f7a8b9c0d",
    "firstName": "John",
    "lastName": "Doe",
    "email": "john@example.com"
  }
}

Sets Cookie: refreshToken (HTTP-only, 7 days)

3. Profile - GET /api/auth/profile

Headers:

Authorization: Bearer <access_token>

Response:

{
  "user": {
    "_id": "650f1a2b3c4d5e6f7a8b9c0d",
    "firstName": "John",
    "lastName": "Doe",
    "emailId": "john@example.com",
    "createdAt": "2023-09-22T10:30:00.000Z",
    "updatedAt": "2023-09-22T10:30:00.000Z"
  }
}

4. Refresh Token - POST /api/auth/refresh

Request: (Cookie automatically sent)

Cookie: refreshToken=<token>

Response:

{
  "accessToken": "eyJhbGc..."
}

5. Logout - POST /api/auth/logout

Request: (Cookie automatically sent)

Cookie: refreshToken=<token>

Response:

{
  "message": "Logged out successfully"
}

Clears Cookie: refreshToken

πŸ“¦ Installation

  1. Clone the repository
git clone <repository-url>
cd auth-system
  1. Install dependencies
npm install
  1. Create .env file
cp .env.example .env
  1. Start MongoDB
# Local MongoDB
mongod

# Or use MongoDB Atlas (cloud)
  1. Run the server
# Development
npm run dev

# Production
npm start

πŸ›‘οΈ Security Features

1. Password Security

  • bcrypt hashing with salt rounds (10)
  • Passwords never stored in plain text

2. Token Security

  • Access tokens: JWT with 15-minute expiry
  • Refresh tokens: Cryptographically random (320 bits), HTTP-only cookies
  • Refresh tokens hashed in database (SHA-256)
  • Automatic cleanup via MongoDB TTL indexes

3. HTTP Security

  • Helmet.js for security headers
  • CORS protection
  • XSS protection
  • HSTS in production

4. Request Security

  • Rate limiting (100 requests per 15 minutes)
  • Input validation (basic checks)
  • Secure cookie flags (HTTP-only, Secure, SameSite)

5. Database Security

  • Tokens hashed before storage
  • No plain-text secrets
  • Indexed for performance

πŸ”„ Authentication Flow

Signup/Login Flow

  1. Client sends credentials
  2. Server validates and creates/verifies user
  3. Generates access token (JWT) and refresh token (random)
  4. Stores hashed refresh token in MongoDB with user reference
  5. Sets refresh token as HTTP-only cookie
  6. Sends access token in response body

Authenticated Request Flow

  1. Client includes access token in Authorization header
  2. Middleware verifies JWT signature and expiry
  3. If valid, attaches user to request and proceeds
  4. If expired, client should call refresh endpoint

Token Refresh Flow

  1. Client calls /refresh endpoint (cookie auto-sent)
  2. Server hashes cookie value and looks up in database
  3. Verifies token exists and not expired
  4. Generates new access token
  5. Returns new access token (same refresh token remains)

Logout Flow

  1. Client calls /logout endpoint
  2. Server hashes refresh token from cookie
  3. Deletes matching token from database
  4. Clears refresh token cookie
  5. Client discards access token

🚨 Error Handling

HTTP Status Codes

  • 200 - Success
  • 201 - Resource created
  • 400 - Bad request (missing fields)
  • 401 - Unauthorized (missing/invalid token)
  • 403 - Forbidden (token expired/invalid)
  • 404 - Resource not found
  • 409 - Conflict (email already exists)
  • 500 - Server error

Common Error Responses

{ "message": "Email already registered" }
{ "message": "Invalid email or password" }
{ "message": "Access token missing" }
{ "message": "Invalid or expired access token" }
{ "message": "Refresh token missing" }
{ "message": "Invalid refresh token" }
{ "message": "Refresh token expired" }

πŸ’» Frontend Integration Guide

1. Store Access Token

// In memory (recommended)
let accessToken = null;

// After login/signup
accessToken = response.data.accessToken;

2. Make Authenticated Requests

fetch('/api/auth/profile', {
  headers: {
    'Authorization': `Bearer ${accessToken}`
  }
});

3. Handle 401 Responses

const handleRequest = async () => {
  try {
    const response = await fetch('/api/protected', {
      headers: { 'Authorization': `Bearer ${accessToken}` }
    });
    
    if (response.status === 401) {
      // Try to refresh
      const refreshResponse = await fetch('/api/auth/refresh', {
        method: 'POST',
        credentials: 'include' // Important: send cookies
      });
      
      if (refreshResponse.ok) {
        const { accessToken: newToken } = await refreshResponse.json();
        accessToken = newToken;
        // Retry original request
      } else {
        // Redirect to login
        window.location.href = '/login';
      }
    }
  } catch (error) {
    console.error('Request failed:', error);
  }
};

4. Logout

const logout = async () => {
  await fetch('/api/auth/logout', {
    method: 'POST',
    credentials: 'include'
  });
  accessToken = null;
  window.location.href = '/login';
};

5. Initial Load

// On app start, try to refresh
const initializeAuth = async () => {
  try {
    const response = await fetch('/api/auth/refresh', {
      method: 'POST',
      credentials: 'include'
    });
    
    if (response.ok) {
      const { accessToken: newToken } = await response.json();
      accessToken = newToken;
      return true;
    }
  } catch (error) {
    console.error('Auto-login failed:', error);
  }
  return false;
};

πŸ”œ Future Enhancements

  • Token rotation for refresh tokens
  • Device fingerprinting
  • Email verification
  • Password reset flow
  • Role-based access control (RBAC)
  • Session management dashboard
  • Two-factor authentication (2FA)
  • OAuth2 integration

πŸ“ License

MIT


Note: This is a production-grade implementation but always conduct security audits and penetration testing before deploying to production with real user data.

About

Production-grade Node.js/Express authentication system with JWT access tokens (15min) and HTTP-only refresh tokens (7 days) in MongoDB. Features bcrypt password hashing, token hashing, rate limiting, Helmet security headers, and complete auth flow: signup, login, profile, refresh, logout. Refresh tokens stored hashed with automatic cleanup via TTL

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors