api-v1/AUTH_IMPLEMENTATION.md

425 lines
11 KiB
Markdown

# 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`**:
```javascript
{
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**:
```javascript
'/admin/*' ['ADMIN']
'/users/*' ['USER', 'ADMIN']
'/orders/*' ['USER', 'ADMIN']
'/listings/*' ['USER', 'ADMIN']
'/locations/*' ['USER', 'ADMIN']
'/chat/*' ['USER', 'ADMIN']
'*' ['USER', 'ADMIN'] // Default
```
**Usage**:
```javascript
// 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**:
```javascript
router.get('/users/:id',
fineAuthorize({
action: 'read',
resource: 'user',
getResourceOwnerId: (req) => req.params.id,
}),
handler
);
```
**Helper Function**:
```javascript
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**:
```javascript
// 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:
```env
# 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
```bash
# 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)
```bash
curl -X GET http://localhost:3200/users/user-123
# Returns: 401 Unauthorized
```
### Test with Invalid Token (Should return 401)
```bash
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)
```bash
# 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**:
```bash
cd Backend
npm install
```
2. **Configure Environment**:
```bash
cp .env.example .env
# Edit .env with your JWT_ACCESS_SECRET from auth service
```
3. **Start Server**:
```bash
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