UNPKG

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
# 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! 🎉