425 lines
11 KiB
Markdown
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
|