self-serve-integration-service
Version:
Self-Serve Integration Service for managing multiple funder integrations including REST APIs, SOAP APIs, and UI automation
316 lines (244 loc) • 8.77 kB
Markdown
# JWT Authentication System
## Overview
The Integration Service now implements the same JWT authentication system as the Document Service, providing consistent security across the self-serve platform.
## Authentication Flow
1. **Client Request**: Client includes JWT token in Authorization header
2. **Token Validation**: Middleware verifies token using RS256 algorithm with public key
3. **User Context**: User information is extracted and attached to request
4. **Route Access**: Protected routes become accessible with valid authentication
## Configuration
### Environment Variables
The following environment variables must be configured in `.env`:
```bash
# JWT Configuration (use RS256 in prod)
# Public key for verifying tokens issued by Auth Service
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n"
JWT_ISSUER=self-serve-platform
JWT_AUDIENCE=self-serve-users
```
### Configuration File
Environment variables are centralized in `src/config/index.ts`:
```typescript
const config: EnvironmentVariables = {
PORT: process.env['PORT'] || '3003',
NODE_ENV: process.env['NODE_ENV'] || 'development',
DATABASE_URL: process.env['DATABASE_URL'] || '',
JWT_PUBLIC_KEY: process.env['JWT_PUBLIC_KEY'] || undefined,
JWT_ISSUER: process.env['JWT_ISSUER'] || 'self-serve-platform',
JWT_AUDIENCE: process.env['JWT_AUDIENCE'] || 'self-serve-users',
LOG_LEVEL: process.env['LOG_LEVEL'] || 'info',
};
```
## Protected Routes
The following routes are protected by JWT authentication:
### Integration Routes
- `POST /api/integration/*` - All integration endpoints
### Entity Routes
- `GET/POST /api/vehicles` - Vehicle management
- `GET/POST /api/customers` - Customer management
- `GET/POST /api/quotes` - Quote management
- `GET/POST /api/proposals` - Proposal management
- `GET/POST /api/funders` - Funder management
### LEX API Routes
- `POST /api/lex/calculateQuote` - Calculate LEX quote
- `POST /api/lex/generateQuote` - Generate LEX quote
### Alphabet API Routes
- `POST /api/alphabet/quotes/calculate` - Calculate Alphabet quote
- `POST /api/alphabet/quotes/generate` - Generate Alphabet quote
## Public Routes
The following routes do NOT require authentication:
- `GET /health` - Health check endpoint
- `POST /webhooks/*` - Webhook endpoints (validated via webhook signatures)
- `GET /api-docs` - Swagger documentation
## Implementation Details
### Authentication Middleware
Location: `src/middleware/authMiddleware.ts`
The middleware:
1. Extracts JWT token from Authorization header (Bearer scheme)
2. Verifies token using RS256 algorithm with public key
3. Validates token issuer and audience
4. Checks token expiration
5. Extracts user information (userId, email, name, role)
6. Attaches user context to request object
```typescript
const authMiddleware = (req: Request, res: Response, next: NextFunction): void => {
try {
const authHeader = req.headers.authorization;
if (!authHeader) {
res.status(401).json({
error: 'Unauthorized',
message: 'Authorization header is required'
});
return;
}
const token = authHeader.startsWith('Bearer ')
? authHeader.substring(7)
: authHeader;
// Verify token with public key (RS256)
const decoded = jwt.verify(token, publicKey, {
issuer: process.env['JWT_ISSUER'],
audience: process.env['JWT_AUDIENCE'],
algorithms: ['RS256']
}) as JWTUser;
// Attach user to request
(req as AuthenticatedRequest).user = decoded;
(req as AuthenticatedRequest).userId = decoded.sub || decoded.userId || decoded.id || '';
next();
} catch (error) {
// Handle token errors (expired, invalid, etc.)
}
};
```
### Type Definitions
Location: `src/types/index.ts`
```typescript
// JWT User interface
export interface JWTUser {
userId: string;
email?: string;
name?: string;
sub?: string;
id?: string;
role?: string;
iat?: number;
exp?: number;
}
// Extended Request interface with user
export interface AuthenticatedRequest extends Request {
user: JWTUser;
userId: string;
}
```
### Route Protection
Location: `src/index.ts`
```typescript
// Protected API routes (require authentication)
app.use('/api/integration', authMiddleware, integrationRoutes);
app.use('/api/vehicles', authMiddleware, vehicleRoutes);
app.use('/api/customers', authMiddleware, customerRoutes);
app.use('/api/quotes', authMiddleware, quoteRoutes);
app.use('/api/proposals', authMiddleware, proposalRoutes);
app.use('/api/funders', authMiddleware, funderRoutes);
```
## Usage Examples
### Making Authenticated Requests
```bash
# Example: Calculate LEX Quote
curl -X POST http://localhost:3003/api/lex/calculateQuote \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"asset": {
"cap_id": 12345,
"model_year": 2020.5
},
"financials": {
"term": 36,
"mileage": 12000
}
}'
```
### Accessing User Context in Routes
```typescript
import { AuthenticatedRequest } from '../types';
router.post('/quotes', async (req: AuthenticatedRequest, res: Response) => {
// Access authenticated user information
const userId = req.userId;
const userEmail = req.user.email;
// Use user context in business logic
const quote = await quoteService.createQuote({
...req.body,
userId: userId
});
res.json({ success: true, data: quote });
});
```
## Error Responses
### Missing Authorization Header
```json
{
"error": "Unauthorized",
"message": "Authorization header is required"
}
```
### Invalid Token
```json
{
"error": "Unauthorized",
"message": "Invalid token"
}
```
### Expired Token
```json
{
"error": "Unauthorized",
"message": "Token has expired"
}
```
### JWT Not Configured
```json
{
"error": "Internal Server Error",
"message": "JWT verification not configured"
}
```
## Security Considerations
1. **RS256 Algorithm**: Uses asymmetric encryption (public/private key pair)
2. **Token Expiration**: Automatically rejects expired tokens
3. **Issuer Validation**: Verifies token was issued by trusted auth service
4. **Audience Validation**: Ensures token is intended for this service
5. **Secure Headers**: Bearer token scheme for Authorization header
6. **HTTPS Only**: In production, all requests should use HTTPS
## Testing
### Testing with Valid Token
```typescript
describe('Protected Routes', () => {
it('should access protected route with valid token', async () => {
const token = generateValidToken();
const response = await request(app)
.post('/api/quotes/calculate')
.set('Authorization', `Bearer ${token}`)
.send(quoteData);
expect(response.status).toBe(200);
});
});
```
### Testing without Token
```typescript
describe('Protected Routes', () => {
it('should reject access without token', async () => {
const response = await request(app)
.post('/api/quotes/calculate')
.send(quoteData);
expect(response.status).toBe(401);
expect(response.body.error).toBe('Unauthorized');
});
});
```
## Troubleshooting
### Common Issues
1. **"JWT verification not configured"**
- Solution: Ensure `JWT_PUBLIC_KEY` is set in environment variables
2. **"Invalid token"**
- Solution: Check that the public key matches the private key used to sign tokens
- Solution: Verify the token format is valid JWT
3. **"Token has expired"**
- Solution: Request a new token from the auth service
4. **Public key format issues**
- Solution: Ensure newlines are properly escaped as `\n` in environment variables
- Solution: Verify the public key includes BEGIN and END markers
## Migration Checklist
- [x] Add JWT configuration to `env.example`
- [x] Create config file (`src/config/index.ts`)
- [x] Update auth middleware to match document service
- [x] Apply auth middleware to protected routes
- [x] Remove debug console.log statements
- [x] Verify type definitions are consistent
- [x] Document authentication system
- [ ] Update API documentation (Swagger)
- [ ] Add authentication tests
- [ ] Update deployment configuration with JWT public key
## Related Documentation
- [Document Service Authentication](../../self-serve-document-service/src/middleware/auth.ts)
- [API Gateway Authentication](../../self-serve-api-gateway/README.md)
- [Auth Service Documentation](../../self-serve-api/README.md)