api-v1/AUTH_IMPLEMENTATION.md

11 KiB

Authentication & Authorization Implementation

This document describes the complete authentication and authorization system implemented for BuySellService.

Overview

The system implements a comprehensive security middleware chain that:

  1. Validates JWT tokens from Authorization header
  2. Applies rate limiting (per userId, fallback to IP)
  3. Enforces coarse-grained authorization (route level)
  4. Enforces fine-grained authorization (business logic)
  5. Logs all API requests for audit

Middleware Chain Order

The middleware is applied in this critical order:

1. requestContext     → Extract IP, user agent, request ID
2. auditLogger        → Attach audit logger to request
3. jwtAuthenticate    → Validate JWT token, extract user info
4. rateLimiter        → Apply rate limiting
5. coarseAuthorize    → Route-level role checking
6. fineAuthorize      → Business logic authorization (per route)
7. routeHandler       → Actual business logic

Implementation Details

1. Request Context (middleware/requestContext.js)

Purpose: Extract and attach request metadata

Extracts:

  • Client IP (considers proxies)
  • User agent
  • Request ID (for tracing)
  • Request timestamp

Usage: Applied globally first in middleware chain


2. JWT Authentication (middleware/jwtAuthenticate.js)

Purpose: Validate JWT tokens and extract user information

Validates:

  • Token signature
  • Token expiry
  • Issuer (iss)
  • Audience (aud)

Token Validation:

  • Calls auth service API: All token validation is done by the auth service
  • Endpoint: POST /auth/validate-token on auth service
  • Auth service validates: Signature, expiry, issuer, audience, token_version (for logout-all)
  • Response: Returns { valid: true, payload: {...} } or { valid: false, error: "..." }

Extracts to req.user:

{
  userId: payload.sub,        // User ID
  role: payload.role,          // User role (USER, ADMIN)
  userType: payload.user_type,
  phoneNumber: payload.phone_number,
  tokenVersion: payload.token_version,
  highAssurance: payload.high_assurance,
  tenantId: payload.tenant_id, // For multi-tenant apps
}

Error Responses:

  • 401 Unauthorized - Missing or invalid token

Environment Variables:

  • AUTH_SERVICE_URL (required) - URL of the auth service (default: 'http://localhost:3000')
  • AUTH_SERVICE_TIMEOUT (optional) - Timeout for auth service calls in ms (default: 5000)

3. Rate Limiting (middleware/rateLimiter.js)

Purpose: Prevent API abuse by limiting requests per user/IP

Strategy:

  • Preferred: Rate limit per userId (if authenticated)
  • Fallback: Rate limit per IP (if not authenticated)

Storage:

  • Redis (if available) - Distributed rate limiting
  • In-memory (fallback) - Single-instance rate limiting

Pre-configured Limiters:

  • rateLimiterRead - 100 requests per 15 minutes (GET operations)
  • rateLimiterWrite - 20 requests per 15 minutes (POST, PUT, DELETE)
  • rateLimiterSensitive - 10 requests per hour (sensitive operations)

Response Headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 2024-01-20T15:30:00Z
X-RateLimit-Type: read

Error Responses:

  • 429 Too Many Requests - Rate limit exceeded

Environment Variables:

  • REDIS_URL (optional) - Redis connection URL
  • RATE_LIMIT_MAX_REQUESTS (optional, default: 100)
  • RATE_LIMIT_WINDOW_SECONDS (optional, default: 900)

4. Coarse-Grained Authorization (middleware/coarseAuthorize.js)

Purpose: Route-level role-based access control

Route-to-Role Mapping:

'/admin/*'      ['ADMIN']
'/users/*'      ['USER', 'ADMIN']
'/orders/*'     ['USER', 'ADMIN']
'/listings/*'   ['USER', 'ADMIN']
'/locations/*'  ['USER', 'ADMIN']
'/chat/*'       ['USER', 'ADMIN']
'*'             ['USER', 'ADMIN']  // Default

Usage:

// Use route mapping
const requireUserOrAdmin = createCoarseAuthorize();

// Or specify roles explicitly
const requireAdmin = createCoarseAuthorize(['ADMIN']);

Error Responses:

  • 403 Forbidden - User role doesn't match required roles

5. Fine-Grained Authorization (middleware/fineAuthorize.js)

Purpose: Business logic level authorization (resource ownership, action permissions)

Authorization Rules:

Users:

  • Users can read their own profile
  • Users can update their own profile
  • Users cannot access other users' data (unless admin)

Orders:

  • Users can create orders
  • Users can read/update/delete their own orders
  • Users cannot access other users' orders

Listings:

  • Users can create listings
  • Users can read all active listings (public)
  • Users can update/delete their own listings

Usage:

router.get('/users/:id', 
  fineAuthorize({
    action: 'read',
    resource: 'user',
    getResourceOwnerId: (req) => req.params.id,
  }),
  handler
);

Helper Function:

const result = authorizeAction({
  user: req.user,
  action: 'read',
  resource: 'user',
  resourceOwnerId: userId,
});

if (!result.authorized) {
  // Handle unauthorized
}

Error Responses:

  • 403 Forbidden - Action not authorized on resource

6. Audit Logging (services/auditLogger.js)

Purpose: Log all API requests for security auditing

Logged Information:

  • Timestamp
  • Request ID
  • User ID
  • Action
  • Route
  • HTTP Method
  • Status (success, failed, forbidden, etc.)
  • Client IP
  • User Agent
  • Additional metadata

Usage:

// Automatic logging via middleware
req.auditLogger.logSuccess('get_user', { userId: id });
req.auditLogger.logFailure('get_user', 'User not found', { userId: id });
req.auditLogger.logForbidden('get_user', 'Access denied', { userId: id });

Storage:

  • Currently logs to console and in-memory store
  • TODO: Integrate with external logging service (CloudWatch, Elasticsearch, etc.)

Example: GET /users/:userId Endpoint

Here's how the complete flow works for GET /users/:userId:

1. Request arrives with: Authorization: Bearer <token>

2. requestContext
   → Extracts IP: 192.168.1.1
   → Extracts User-Agent: Android App
   → Generates Request-ID: abc123

3. auditLogger
   → Attaches logger to req.auditLogger

4. jwtAuthenticate
   → Validates JWT token
   → Extracts: userId = "user-123", role = "USER"
   → Sets: req.user = { userId: "user-123", role: "USER", ... }

5. rateLimiterRead
   → Checks rate limit for user-123
   → Count: 5/100 (under limit)
   → Sets headers: X-RateLimit-Remaining: 95

6. coarseAuthorize
   → Checks route: /users/:id
   → Required roles: ['USER', 'ADMIN']
   → User role: 'USER' ✅
   → Allows request

7. fineAuthorize
   → Action: 'read'
   → Resource: 'user'
   → Resource owner: req.params.id = "user-456"
   → Checks: Can user-123 read user-456?
   → Rule: Users can only read their own profile
   → Result: user-123 !== user-456 → ❌ Unauthorized
   → Returns: 403 Forbidden (unless user-123 === user-456 or user-123 is ADMIN)

8. routeHandler
   → Fetches user from database
   → Returns user data
   → Logs success via req.auditLogger

Configuration

Environment Variables

Create a .env file:

# Server
PORT=3200
TRUST_PROXY=false

# JWT (REQUIRED - must match auth service)
JWT_ACCESS_SECRET=your_jwt_secret_here
JWT_ISSUER=farm-auth-service
JWT_AUDIENCE=mobile-app

# Rate Limiting (Optional)
REDIS_URL=redis://localhost:6379
RATE_LIMIT_READ_MAX=100
RATE_LIMIT_READ_WINDOW=900

# Auth Service (Optional - for centralized validation)
VALIDATE_VIA_AUTH_SERVICE=false
AUTH_SERVICE_URL=http://localhost:3000

Testing

Test with Valid Token

# Get token from auth service (via login)
TOKEN="your_access_token_here"

# Test GET /users/:userId
curl -X GET http://localhost:3200/users/user-123 \
  -H "Authorization: Bearer $TOKEN"

Test without Token (Should return 401)

curl -X GET http://localhost:3200/users/user-123
# Returns: 401 Unauthorized

Test with Invalid Token (Should return 401)

curl -X GET http://localhost:3200/users/user-123 \
  -H "Authorization: Bearer invalid_token"
# Returns: 401 Unauthorized

Test Rate Limiting (Should return 429 after limit)

# Make many rapid requests
for i in {1..101}; do
  curl -X GET http://localhost:3200/users/user-123 \
    -H "Authorization: Bearer $TOKEN"
done
# After 100 requests, returns: 429 Too Many Requests

Security Features

JWT Token Validation: Signature, expiry, issuer, audience
Rate Limiting: Per-user and per-IP
Role-Based Access Control: Route-level and resource-level
Audit Logging: All requests logged with context
No Cookies: Uses Bearer tokens only
No Server-Side Token Storage: Stateless JWT validation
HTTPS Assumed: No certificate handling in app code


Files Structure

Backend/
├── middleware/
│   ├── requestContext.js      # Request metadata extraction
│   ├── jwtAuthenticate.js     # JWT token validation
│   ├── rateLimiter.js         # Rate limiting
│   ├── coarseAuthorize.js     # Route-level authorization
│   └── fineAuthorize.js       # Business logic authorization
├── services/
│   └── auditLogger.js         # Audit logging service
├── routes/
│   └── userRoutes.js          # User routes with auth middleware
└── server.js                  # Server setup with middleware chain

Next Steps

  1. Install Dependencies:

    cd Backend
    npm install
    
  2. Configure Environment:

    cp .env.example .env
    # Edit .env with your JWT_ACCESS_SECRET from auth service
    
  3. Start Server:

    npm start
    
  4. Test Endpoint:

    • Use Android app to call GET /users/:userId
    • Verify authentication and authorization work correctly

Integration with Auth Service

The BuySellService calls the auth service API to validate JWT tokens. All token validation logic is centralized in the auth service.

Token Flow:

  1. User logs in via auth service → Receives JWT token
  2. Android app stores token securely
  3. Android app sends token in Authorization: Bearer <token> header to BuySellService
  4. BuySellService extracts token and calls auth service: POST /auth/validate-token
  5. Auth service validates token (signature, expiry, token_version, claims) and returns user info
  6. BuySellService receives validated user info and attaches to req.user
  7. BuySellService continues with authorization checks and processes request

Auth Service Endpoint:

  • POST /auth/validate-token
  • Request: { token: "..." }
  • Response (valid): { valid: true, payload: { sub, role, user_type, ... } }
  • Response (invalid): { valid: false, error: "..." }

Benefits of Centralized Validation:

  • Single source of truth for token validation
  • Token version checking (for logout-all functionality) handled centrally
  • No need to share JWT secrets between services
  • Easier to update validation logic in one place