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:
- ✅ Validates JWT tokens from Authorization header
- ✅ Applies rate limiting (per userId, fallback to IP)
- ✅ Enforces coarse-grained authorization (route level)
- ✅ Enforces fine-grained authorization (business logic)
- ✅ 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-tokenon 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 URLRATE_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
-
Install Dependencies:
cd Backend npm install -
Configure Environment:
cp .env.example .env # Edit .env with your JWT_ACCESS_SECRET from auth service -
Start Server:
npm start -
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:
- User logs in via auth service → Receives JWT token
- Android app stores token securely
- Android app sends token in
Authorization: Bearer <token>header to BuySellService - BuySellService extracts token and calls auth service:
POST /auth/validate-token - Auth service validates token (signature, expiry, token_version, claims) and returns user info
- BuySellService receives validated user info and attaches to
req.user - 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