API-Explorer-II/MULTI-OIDC-PROVIDER-SUMMARY.md
simonredfern 743038953d Add multi-OIDC provider backend services
- Add TypeScript interfaces for multi-provider OAuth2 support
- Create OAuth2ClientWithConfig extending arctic OAuth2Client with OIDC discovery
- Create OAuth2ProviderFactory with strategy pattern for different providers
- Create OAuth2ProviderManager for managing multiple providers with health checks
- Support for OBP-OIDC, Keycloak, Google, GitHub, and custom providers
2026-01-14 13:00:45 +01:00

373 lines
9.6 KiB
Markdown

# Multi-OIDC Provider Implementation - Executive Summary
## Overview
This document provides a high-level summary of implementing multiple OIDC provider support in API Explorer II, based on the proven architecture from OBP-Portal.
---
## Current State
**API Explorer II** currently supports OAuth2/OIDC authentication with a **single provider** configured via environment variables:
```bash
VITE_OBP_OAUTH2_WELL_KNOWN_URL=http://localhost:9000/obp-oidc/.well-known/openid-configuration
VITE_OBP_OAUTH2_CLIENT_ID=<client-id>
VITE_OBP_OAUTH2_CLIENT_SECRET=<client-secret>
```
**Limitations:**
- Only one OIDC provider supported at a time
- No user choice of authentication method
- Requires redeployment to switch providers
- No fallback if provider is unavailable
---
## Target State
**Multi-Provider Support** allows users to choose from multiple identity providers at login:
- **OBP-OIDC** - Open Bank Project's identity provider
- **Keycloak** - Enterprise identity management
- **Google** - Consumer identity (optional)
- **GitHub** - Developer identity (optional)
- **Custom** - Any OpenID Connect provider
---
## How OBP-Portal Does It
### 1. Dynamic Provider Discovery
OBP-Portal fetches available OIDC providers from the **OBP API**:
```
GET /obp/v5.1.0/well-known
```
**Response:**
```json
{
"well_known_uris": [
{
"provider": "obp-oidc",
"url": "http://localhost:9000/obp-oidc/.well-known/openid-configuration"
},
{
"provider": "keycloak",
"url": "http://localhost:8180/realms/obp/.well-known/openid-configuration"
}
]
}
```
### 2. Provider Manager
**Key Component:** `OAuth2ProviderManager`
**Responsibilities:**
- Fetch well-known URIs from OBP API
- Initialize OAuth2 client for each provider
- Track provider health (available/unavailable)
- Perform periodic health checks (60s intervals)
- Provide access to specific providers
### 3. Provider Factory
**Key Component:** `OAuth2ProviderFactory`
**Responsibilities:**
- Strategy pattern for provider-specific configuration
- Load credentials from environment variables
- Create OAuth2 clients with OIDC discovery
- Support multiple provider types
**Strategy Pattern:**
```typescript
strategies.set('obp-oidc', {
clientId: process.env.VITE_OBP_OAUTH2_CLIENT_ID,
clientSecret: process.env.VITE_OBP_OAUTH2_CLIENT_SECRET,
redirectUri: process.env.VITE_OBP_OAUTH2_REDIRECT_URL
})
strategies.set('keycloak', {
clientId: process.env.VITE_KEYCLOAK_CLIENT_ID,
clientSecret: process.env.VITE_KEYCLOAK_CLIENT_SECRET,
redirectUri: process.env.VITE_KEYCLOAK_REDIRECT_URL
})
```
### 4. User Flow
```
1. User clicks "Login"
→ Shows provider selection dialog
2. User selects provider (e.g., "OBP-OIDC")
→ GET /api/oauth2/connect?provider=obp-oidc
3. Server:
- Retrieves OAuth2 client for "obp-oidc"
- Generates PKCE parameters
- Stores provider name in session
- Redirects to provider's authorization endpoint
4. User authenticates on selected OIDC provider
5. Provider redirects back:
→ GET /api/oauth2/callback?code=xxx&state=yyy
6. Server:
- Retrieves provider from session ("obp-oidc")
- Gets corresponding OAuth2 client
- Exchanges code for tokens
- Stores tokens with provider name
7. User authenticated with selected provider
```
---
## Implementation Architecture for API Explorer II
### New Services
#### 1. **OAuth2ClientWithConfig** (extends `OAuth2Client` from arctic)
```typescript
class OAuth2ClientWithConfig extends OAuth2Client {
public OIDCConfig?: OIDCConfiguration
public provider: string
async initOIDCConfig(oidcConfigUrl: string): Promise<void>
getAuthorizationEndpoint(): string
getTokenEndpoint(): string
getUserInfoEndpoint(): string
}
```
#### 2. **OAuth2ProviderFactory**
```typescript
class OAuth2ProviderFactory {
private strategies: Map<string, ProviderStrategy>
async initializeProvider(wellKnownUri: WellKnownUri): Promise<OAuth2ClientWithConfig>
getConfiguredProviders(): string[]
}
```
#### 3. **OAuth2ProviderManager**
```typescript
class OAuth2ProviderManager {
private providers: Map<string, OAuth2ClientWithConfig>
async fetchWellKnownUris(): Promise<WellKnownUri[]>
async initializeProviders(): Promise<boolean>
getProvider(providerName: string): OAuth2ClientWithConfig
getAvailableProviders(): string[]
startHealthCheck(intervalMs: number): void
}
```
### Updated Controllers
#### 1. **OAuth2ProvidersController** (NEW)
```typescript
GET /api/oauth2/providers
Returns: { providers: [...], count: 2, availableCount: 1 }
```
#### 2. **OAuth2ConnectController** (UPDATED)
```typescript
GET /api/oauth2/connect?provider=obp-oidc&redirect=/resource-docs
Redirects to selected provider's authorization endpoint
```
#### 3. **OAuth2CallbackController** (UPDATED)
```typescript
GET /api/oauth2/callback?code=xxx&state=yyy
Uses provider from session to exchange code for tokens
```
### Frontend Updates
#### **HeaderNav.vue** (UPDATED)
**Before:**
```vue
<a href="/api/oauth2/connect">Login</a>
```
**After:**
```vue
<button @click="handleLoginClick">
Login
<span v-if="availableProviders.length > 1">▼</span>
</button>
<!-- Provider Selection Dialog -->
<el-dialog v-model="showProviderSelector">
<div v-for="provider in availableProviders">
<div @click="loginWithProvider(provider.name)">
{{ provider.name }}
</div>
</div>
</el-dialog>
```
---
## Configuration
### Environment Variables
```bash
# OBP-OIDC Provider
VITE_OBP_OAUTH2_CLIENT_ID=48ac28e9-9ee3-47fd-8448-69a62764b779
VITE_OBP_OAUTH2_CLIENT_SECRET=fOTQF7jfg8C74u7ZhSjVQpoBYvD0KpWfM5UsEZBSFFM
VITE_OBP_OAUTH2_REDIRECT_URL=http://localhost:5173/api/oauth2/callback
# Keycloak Provider
VITE_KEYCLOAK_CLIENT_ID=obp-api-explorer
VITE_KEYCLOAK_CLIENT_SECRET=your-keycloak-secret
VITE_KEYCLOAK_REDIRECT_URL=http://localhost:5173/api/oauth2/callback
# Google Provider (Optional)
VITE_GOOGLE_CLIENT_ID=your-google-client-id
VITE_GOOGLE_CLIENT_SECRET=your-google-client-secret
VITE_GOOGLE_REDIRECT_URL=http://localhost:5173/api/oauth2/callback
```
**Note:** No need to specify well-known URLs - they are fetched from OBP API!
---
## Key Benefits
### 1. **Dynamic Discovery**
- Providers are discovered from OBP API at runtime
- No hardcoded provider list
- Easy to add new providers without code changes
### 2. **User Choice**
- Users select their preferred authentication method
- Better user experience
- Support for organizational identity preferences
### 3. **Resilience**
- Health monitoring detects provider outages
- Can fallback to alternative providers
- Automatic retry for failed initializations
### 4. **Extensibility**
- Strategy pattern makes adding providers trivial
- Just add environment variables
- No code changes needed
### 5. **Backward Compatibility**
- Existing single-provider mode still works
- Gradual migration path
- No breaking changes
---
## Implementation Phases
### **Phase 1: Backend Services** (Week 1)
- [ ] Create `OAuth2ClientWithConfig`
- [ ] Create `OAuth2ProviderFactory`
- [ ] Create `OAuth2ProviderManager`
- [ ] Create TypeScript interfaces
### **Phase 2: Backend Controllers** (Week 1-2)
- [ ] Create `OAuth2ProvidersController`
- [ ] Update `OAuth2ConnectController` with provider parameter
- [ ] Update `OAuth2CallbackController` to use provider from session
### **Phase 3: Frontend** (Week 2)
- [ ] Update `HeaderNav.vue` to fetch providers
- [ ] Add provider selection UI (dialog/dropdown)
- [ ] Update login flow to include provider selection
### **Phase 4: Configuration & Testing** (Week 2-3)
- [ ] Configure environment variables for multiple providers
- [ ] Write unit tests
- [ ] Write integration tests
- [ ] Manual testing with OBP-OIDC and Keycloak
- [ ] Update documentation
---
## Migration Path
### **Step 1: Deploy with Backward Compatibility**
- Implement new services
- Keep existing single-provider mode working
- Test thoroughly
### **Step 2: Enable Multi-Provider**
- Add provider environment variables
- Enable provider selection UI
- Monitor for issues
### **Step 3: Deprecate Single-Provider**
- Update documentation
- Remove `VITE_OBP_OAUTH2_WELL_KNOWN_URL` env variable
- Use OBP API well-known endpoint by default
---
## Testing Strategy
### Unit Tests
- `OAuth2ProviderFactory.test.ts` - Strategy creation
- `OAuth2ProviderManager.test.ts` - Provider initialization
- `OAuth2ClientWithConfig.test.ts` - OIDC config loading
### Integration Tests
- Multi-provider login flow
- Provider selection
- Token exchange with different providers
- Callback handling
### Manual Testing
- Login with OBP-OIDC
- Login with Keycloak
- Provider unavailable scenarios
- Network error handling
- User cancellation
---
## Success Criteria
- ✅ Users can choose from multiple OIDC providers
- ✅ Providers are discovered from OBP API automatically
- ✅ Health monitoring detects provider outages
- ✅ Backward compatible with single-provider mode
- ✅ No code changes needed to add new providers (only env vars)
- ✅ Comprehensive test coverage (>80%)
- ✅ Documentation updated
---
## References
- **Full Implementation Guide:** `MULTI-OIDC-PROVIDER-IMPLEMENTATION.md`
- **OBP-Portal Reference:** `~/Documents/workspace_2024/OBP-Portal`
- **OBP API Well-Known Endpoint:** `/obp/v5.1.0/well-known`
- **Current OAuth2 Docs:** `OAUTH2-README.md`, `OAUTH2-OIDC-INTEGRATION-PREP.md`
- **Arctic OAuth2 Library:** https://github.com/pilcrowOnPaper/arctic
- **OpenID Connect Discovery:** https://openid.net/specs/openid-connect-discovery-1_0.html
---
## Questions?
For detailed implementation instructions, see **MULTI-OIDC-PROVIDER-IMPLEMENTATION.md**
For OBP-Portal reference implementation, see:
- `OBP-Portal/src/lib/oauth/providerManager.ts`
- `OBP-Portal/src/lib/oauth/providerFactory.ts`
- `OBP-Portal/src/lib/oauth/client.ts`