self-serve-integration-service
Version:
Self-Serve Integration Service for managing multiple funder integrations including REST APIs, SOAP APIs, and UI automation
452 lines (349 loc) • 13.5 kB
Markdown
# Authentication Guide
## Overview
The Integration Service supports **two authentication methods**:
1. **User Authentication** - For frontend/user API calls
2. **Service-to-Service Authentication** - For microservice-to-microservice communication
Both methods use **JWT tokens** in the `Authorization: Bearer` header.
## 1. User Authentication (Frontend → Integration Service)
### Use Case
- Frontend applications calling Integration Service APIs
- User-initiated actions requiring user context
### Authentication Flow
```
┌─────────┐ ┌──────────────┐ ┌─────────────────────┐
│ Frontend│ │ Auth Service │ │ Integration Service │
└────┬────┘ └──────┬───────┘ └──────────┬──────────┘
│ │ │
│ POST /api/auth/login │ │
│────────────────────────────>│ │
│ { email, password } │ │
│ │ │
│ { accessToken, ... } │ │
│<────────────────────────────│ │
│ │ │
│ │ GET /api/integration/quotes │
│ │ Authorization: Bearer <token> │
│─────────────────────────────────────────────────────────────>│
│ │ │
│ │ Validates JWT token │
│ │ Extracts: userId, email │
│ │ │
│ { quotes: [...] } │ │
│<─────────────────────────────────────────────────────────────│
│ │ │
```
### Implementation
#### 1. Login to get user token
```bash
POST http://localhost:3001/api/auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "password123"
}
```
**Response:**
```json
{
"success": true,
"data": {
"user": {
"id": "user-123",
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe"
},
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
```
#### 2. Call Integration Service with user token
```bash
GET http://localhost:3003/api/integration/quotes/calculate
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
```
### Token Payload (User)
```json
{
"sub": "user-123",
"email": "user@example.com",
"iat": 1640995200,
"exp": 1641081600,
"iss": "self-serve-platform",
"aud": "self-serve-users"
}
```
## 2. Service-to-Service Authentication (Microservice → Integration Service)
### Use Case
- Order Service calling Integration Service to get quotes
- Document Service calling Integration Service for proposal data
- Any microservice needing to communicate with Integration Service
### Authentication Flow
```
┌──────────────┐ ┌──────────────┐ ┌─────────────────────┐
│Order Service │ │ Auth Service │ │ Integration Service │
└──────┬───────┘ └──────┬───────┘ └──────────┬──────────┘
│ │ │
│ POST /api/oauth/token │ │
│─────────────────────────>│ │
│ grant_type: client_cre..│ │
│ client_id: order-servic.│ │
│ client_secret: *** │ │
│ │ │
│ { access_token, ... } │ │
│<─────────────────────────│ │
│ │ │
│ │ GET /api/integration/quotes │
│ │ Authorization: Bearer <tok> │
│──────────────────────────────────────────────────────>│
│ │ │
│ │ Validates JWT token │
│ │ Detects: client_type=oauth │
│ │ Extracts: clientId, scopes │
│ │ │
│ { quotes: [...] } │ │
│<──────────────────────────────────────────────────────│
│ │ │
```
### Implementation
#### Step 1: Register OAuth Client (One-time setup)
```bash
POST http://localhost:3001/api/oauth/clients
Authorization: Bearer <admin-token>
Content-Type: application/json
{
"name": "order-service",
"description": "Order Service for processing quotes and orders",
"scopes": ["integration:read", "integration:write"]
}
```
**Response:**
```json
{
"success": true,
"data": {
"clientId": "order-service-client-abc123",
"clientSecret": "secret_xyz789",
"scopes": ["integration:read", "integration:write"],
"isActive": true
}
}
```
⚠️ **Important:** Store `clientId` and `clientSecret` securely in your service's environment variables!
#### Step 2: Exchange Credentials for Access Token
```bash
POST http://localhost:3001/api/oauth/token
Content-Type: application/json
{
"grant_type": "client_credentials",
"client_id": "order-service-client-abc123",
"client_secret": "secret_xyz789"
}
```
**Response:**
```json
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"scope": "integration:read integration:write"
}
```
#### Step 3: Call Integration Service with OAuth Token
```bash
GET http://localhost:3003/api/integration/quotes/calculate
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
```
### Token Payload (OAuth Client)
```json
{
"sub": "order-service-client-abc123",
"client_type": "oauth_client",
"client_id": "order-service-client-abc123",
"scopes": ["integration:read", "integration:write"],
"iat": 1640995200,
"exp": 1640998800,
"iss": "self-serve-platform",
"aud": "self-serve-users"
}
```
## Token Refresh
### User Token Refresh
```bash
POST http://localhost:3001/api/auth/refresh
Content-Type: application/json
{
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```
### OAuth Client Token Refresh
```bash
POST http://localhost:3001/api/oauth/token
Content-Type: application/json
{
"grant_type": "refresh_token",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"client_id": "order-service-client-abc123"
}
```
## Middleware Behavior
The Integration Service's `authMiddleware` automatically detects token type:
### User Token Detection
```typescript
// If token has client_type !== "oauth_client"
req.user = decoded;
req.userId = decoded.sub;
req.clientType = 'user';
```
### OAuth Client Token Detection
```typescript
// If token has client_type === "oauth_client"
req.user = decoded;
req.userId = '';
req.clientId = decoded.client_id;
req.clientType = 'oauth_client';
req.scopes = decoded.scopes;
```
## Environment Variables
### Integration Service
```env
# JWT Configuration (must match auth-service)
JWT_SECRET=your-jwt-secret-key
# OR for asymmetric keys
JWT_PUBLIC_KEY=/path/to/public.key
JWT_ISSUER=self-serve-platform
JWT_AUDIENCE=self-serve-users
```
### Your Microservice (for service-to-service calls)
```env
# OAuth Client Credentials (from auth-service)
AUTH_SERVICE_URL=http://localhost:3001
OAUTH_CLIENT_ID=your-service-client-id
OAUTH_CLIENT_SECRET=your-service-client-secret
INTEGRATION_SERVICE_URL=http://localhost:3003
```
## Example: Service-to-Service Call in Node.js
```typescript
import axios from 'axios';
class IntegrationServiceClient {
private accessToken: string | null = null;
private tokenExpiresAt: number = 0;
constructor(
private authServiceUrl: string,
private integrationServiceUrl: string,
private clientId: string,
private clientSecret: string
) {}
// Get or refresh OAuth token
private async getAccessToken(): Promise<string> {
// Return cached token if still valid
if (this.accessToken && Date.now() < this.tokenExpiresAt) {
return this.accessToken;
}
// Exchange credentials for new token
const response = await axios.post(`${this.authServiceUrl}/api/oauth/token`, {
grant_type: 'client_credentials',
client_id: this.clientId,
client_secret: this.clientSecret,
});
this.accessToken = response.data.access_token;
this.tokenExpiresAt = Date.now() + response.data.expires_in * 1000 - 60000; // Refresh 1 min early
return this.accessToken;
}
// Call Integration Service
async calculateQuote(quoteData: any): Promise<any> {
const token = await this.getAccessToken();
const response = await axios.post(
`${this.integrationServiceUrl}/api/integration/quotes/calculate`,
quoteData,
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
}
);
return response.data;
}
}
// Usage
const client = new IntegrationServiceClient(
process.env.AUTH_SERVICE_URL!,
process.env.INTEGRATION_SERVICE_URL!,
process.env.OAUTH_CLIENT_ID!,
process.env.OAUTH_CLIENT_SECRET!
);
const quote = await client.calculateQuote({
funderName: 'LEX',
vehicleId: 'vehicle-123',
term: 36,
deposit: 5000,
annualMileage: 10000,
});
```
## Security Best Practices
### For User Tokens
1. ✅ Store tokens securely (httpOnly cookies or secure storage)
2. ✅ Never expose tokens in URLs or logs
3. ✅ Implement token refresh before expiration
4. ✅ Clear tokens on logout
### For OAuth Client Tokens
1. ✅ Store `client_secret` in environment variables (never in code)
2. ✅ Use separate OAuth clients per service
3. ✅ Implement token caching to reduce auth-service load
4. ✅ Rotate client secrets periodically
5. ✅ Use appropriate scopes for each service
6. ✅ Monitor and log OAuth token usage
## Troubleshooting
### Common Errors
#### 401 Unauthorized - Missing Authorization header
```json
{
"error": "Unauthorized",
"message": "Authorization header is required"
}
```
**Solution:** Include `Authorization: Bearer <token>` header
#### 401 Unauthorized - Token expired
```json
{
"error": "Unauthorized",
"message": "Token has expired"
}
```
**Solution:** Refresh the token using refresh_token grant
#### 401 Unauthorized - Invalid token
```json
{
"error": "Unauthorized",
"message": "Invalid token"
}
```
**Solution:** Verify JWT_SECRET/JWT_PUBLIC_KEY matches between services
#### 500 Internal Server Error - JWT verification not configured
```json
{
"error": "Internal Server Error",
"message": "JWT verification not configured"
}
```
**Solution:** Set `JWT_SECRET` or `JWT_PUBLIC_KEY` environment variable
## Summary
| Authentication Type | Use Case | Token Type | Token Contains |
| ---------------------- | ----------------- | ---------------- | --------------------------------------------------------- |
| **User Auth** | Frontend → API | User JWT | `sub` (userId), `email` |
| **Service-to-Service** | Service → Service | OAuth Client JWT | `sub` (clientId), `client_type: "oauth_client"`, `scopes` |
Both methods use the same `Authorization: Bearer <token>` header, and the middleware automatically detects the token type! 🎉