# OAuth2/OIDC Integration Preparation Document ## API Explorer II with OBP-OIDC **Version:** 1.0 **Date:** 2024 **Author:** TESOBE Development Team --- ## Table of Contents 1. [Executive Summary](#executive-summary) 2. [Current State Analysis](#current-state-analysis) 3. [Target Architecture](#target-architecture) 4. [OBP-OIDC Overview](#obp-oidc-overview) 5. [OBP-Portal Reference Implementation](#obp-portal-reference-implementation) 6. [Implementation Phases](#implementation-phases) 7. [Technical Requirements](#technical-requirements) 8. [Configuration Changes](#configuration-changes) 9. [Code Changes Required](#code-changes-required) 10. [Testing Strategy](#testing-strategy) 11. [Deployment Considerations](#deployment-considerations) 12. [Rollback Plan](#rollback-plan) 13. [References](#references) --- ## 1. Executive Summary This document outlines the preparation and implementation plan for migrating **API Explorer II** from OAuth 1.0a authentication to OAuth2/OpenID Connect (OIDC) using the **OBP-OIDC** provider. The OBP-Portal serves as the reference implementation for this integration. ### Key Goals - Replace OAuth 1.0a (`oauth` npm package) with OAuth2/OIDC - Integrate with OBP-OIDC provider for authentication - Maintain backward compatibility during transition - Follow OBP-Portal's proven implementation patterns - Support multiple OIDC providers (OBP-OIDC, Keycloak, etc.) ### Benefits - **Modern Authentication**: OAuth2/OIDC is the industry standard - **Better Security**: Improved token management and refresh mechanisms - **User Experience**: Single sign-on capabilities - **Maintainability**: Aligns with OBP ecosystem (Portal, API) - **Flexibility**: Support for multiple identity providers --- ## 2. Current State Analysis ### 2.1 Current Authentication Flow (OAuth 1.0a) API Explorer II currently uses OAuth 1.0a with the following flow: ``` User → /api/connect → OauthRequestTokenMiddleware → OBP-API /oauth/authorize → User Login/Authorization → /api/callback → OauthAccessTokenMiddleware → Session Storage → Authenticated State ``` ### 2.2 Current Implementation Components #### Backend (Express/TypeScript) - **Package**: `oauth` (v0.10.0) - OAuth 1.0a library - **Middlewares**: - `OauthRequestTokenMiddleware.ts` - Handles request token generation - `OauthAccessTokenMiddleware.ts` - Handles access token exchange - **Services**: - `OauthInjectedService.ts` - OAuth consumer management - `OBPClientService.ts` - OBP API client wrapper - **Controllers**: - `ConnectController.ts` - Initiates OAuth flow - `CallbackController.ts` - Handles OAuth callback - **Session Management**: Express-session with Redis store #### Frontend (Vue 3) - **Login Flow**: Redirects to `/api/connect?redirect=` - **User State**: Fetched via `getCurrentUser()` API call - **Components**: - `HeaderNav.vue` - Login/Logoff buttons - `ChatWidget.vue` - Checks authentication for Opey access - `Preview.vue` - Shows login prompts for restricted features #### Environment Configuration ```bash VITE_OBP_CONSUMER_KEY= VITE_OBP_CONSUMER_SECRET= VITE_OBP_REDIRECT_URL=http://localhost:5173/api/callback VITE_OBP_API_HOST=http://127.0.0.1:8080 ``` ### 2.3 Current Limitations 1. **OAuth 1.0a is deprecated** - Industry has moved to OAuth2 2. **No refresh token support** - Users must re-authenticate frequently 3. **Single provider only** - Cannot support multiple identity providers 4. **Complex signature generation** - OAuth 1.0a requires HMAC-SHA1 signatures 5. **Limited ecosystem support** - Most modern services use OAuth2/OIDC --- ## 3. Target Architecture ### 3.1 OAuth2/OIDC Flow ``` User → Login Button → /login/obp → OAuth2 Authorization Endpoint (OBP-OIDC) → User Authentication (OBP-OIDC Login Page) → Authorization Code → /login/obp/callback → Token Exchange → Access Token + Refresh Token + ID Token → Session Storage → Authenticated State ``` ### 3.2 Key Differences from Current Implementation | Aspect | OAuth 1.0a (Current) | OAuth2/OIDC (Target) | | ------------------ | --------------------- | --------------------------------------- | | **Flow** | 3-legged OAuth | Authorization Code Flow with PKCE | | **Tokens** | Access Token + Secret | Access Token + Refresh Token + ID Token | | **Token Type** | Opaque strings | JWT (JSON Web Tokens) | | **Signatures** | HMAC-SHA1 per request | Bearer token in header | | **User Info** | From API calls | From ID token claims | | **Refresh** | Not supported | Refresh token flow | | **Discovery** | Manual configuration | `.well-known/openid-configuration` | | **Multi-Provider** | No | Yes (via provider manager) | --- ## 4. OBP-OIDC Overview ### 4.1 About OBP-OIDC **OBP-OIDC** is a bare-bones OpenID Connect provider built with http4s and functional programming in Scala. It's designed specifically for the OBP ecosystem. - **Repository**: `~/Documents/workspace_2024/OBP-OIDC` - **Technology**: Scala, http4s, PostgreSQL - **Purpose**: Development/testing OIDC provider for OBP apps - **Production Note**: For production, use Keycloak or Hydra ### 4.2 OBP-OIDC Features ✅ **Core OIDC Endpoints**: - Authorization endpoint: `/obp-oidc/auth` - Token endpoint: `/obp-oidc/token` - UserInfo endpoint: `/obp-oidc/userinfo` - JWKS endpoint: `/obp-oidc/jwks` - Discovery: `/obp-oidc/.well-known/openid-configuration` ✅ **Supported Flows**: - Authorization Code Flow - Client Credentials Flow - Refresh Token Flow ✅ **Database Integration**: - Reads from OBP database views - Authenticates against OBP users (`v_oidc_users`) - Manages clients via `v_oidc_clients` and `v_oidc_admin_clients` ✅ **Security Features**: - RS256 JWT signing - BCrypt password verification - Configurable token expiration - PKCE support (recommended) ### 4.3 OBP-OIDC Configuration Default configuration from `run-server.example.sh`: ```bash # Server Configuration OIDC_HOST=localhost OIDC_PORT=9000 # Explorer II Client (Pre-configured) OIDC_CLIENT_EXPLORER_ID=obp-explorer-ii-client OIDC_CLIENT_EXPLORER_SECRET=CHANGE_THIS_TO_EXPLORER_SECRET_2024 OIDC_CLIENT_EXPLORER_REDIRECTS=http://localhost:3001/callback,http://localhost:3001/oauth/callback # Database DB_HOST=localhost DB_PORT=5432 DB_NAME=sandbox OIDC_USER_USERNAME=oidc_user OIDC_USER_PASSWORD= ``` ### 4.4 OBP-OIDC Endpoints **Discovery Document**: ``` GET http://localhost:9000/obp-oidc/.well-known/openid-configuration ``` **Authorization**: ``` GET http://localhost:9000/obp-oidc/auth ?response_type=code &client_id=obp-explorer-ii-client &redirect_uri=http://localhost:5173/login/obp/callback &scope=openid%20profile%20email &state= &code_challenge= &code_challenge_method=S256 ``` **Token Exchange**: ``` POST http://localhost:9000/obp-oidc/token Content-Type: application/x-www-form-urlencoded Authorization: Basic grant_type=authorization_code &code= &redirect_uri=http://localhost:5173/login/obp/callback &code_verifier= ``` **UserInfo**: ``` GET http://localhost:9000/obp-oidc/userinfo Authorization: Bearer ``` --- ## 5. OBP-Portal Reference Implementation ### 5.1 OBP-Portal Architecture The OBP-Portal (`~/Documents/workspace_2024/OBP-Portal`) provides the reference implementation using: - **Framework**: SvelteKit (but patterns are framework-agnostic) - **OAuth2 Library**: `arctic` - Modern OAuth2/OIDC client - **Session Management**: `svelte-kit-sessions` with Redis - **Language**: TypeScript ### 5.2 Key Components from OBP-Portal #### 5.2.1 OAuth2 Client (`src/lib/oauth/client.ts`) ```typescript import { OAuth2Client } from 'arctic' export class OAuth2ClientWithConfig extends OAuth2Client { OIDCConfig?: OpenIdConnectConfiguration async initOIDCConfig(OIDCConfigUrl: string): Promise { // Fetches .well-known/openid-configuration const response = await fetch(OIDCConfigUrl) const config = await response.json() this.OIDCConfig = config } async validateAuthorizationCode( tokenEndpoint: string, code: string, codeVerifier: string | null ): Promise { // Handles token exchange with Basic Auth // Falls back to credentials in body if needed // Supports both OBP-OIDC and Keycloak } } ``` **Key Features**: - Automatic OIDC discovery document parsing - Basic Authentication for client credentials (RFC 6749) - Fallback to form-based credentials for compatibility - Token expiration checking via JWT decoding #### 5.2.2 Provider Manager (`src/lib/oauth/providerManager.ts`) ```typescript class OAuth2ProviderManager { async fetchWellKnownUris(): Promise { // Fetches from OBP API: /obp/v5.1.0/well-known const response = await obp_requests.get('/obp/v5.1.0/well-known') return response.well_known_uris } async initOauth2Providers() { const wellKnownUris = await this.fetchWellKnownUris() for (const providerUri of wellKnownUris) { const oauth2Client = await oauth2ProviderFactory.initializeProvider(providerUri) // Track available/unavailable providers } } } ``` **Key Features**: - Automatic provider discovery from OBP API - Multi-provider support (OBP-OIDC, Keycloak, etc.) - Provider status tracking (available/unavailable) - Automatic retry on initialization failure - Periodic health checks (60 second intervals) #### 5.2.3 Provider Factory (`src/lib/oauth/providerFactory.ts`) ```typescript class OAuth2ProviderFactory { private strategies: Map async initializeProvider(wellKnownUri: WellKnownUri): Promise { const strategy = this.strategies.get(wellKnownUri.provider) if (!strategy) return null const client = new OAuth2ClientWithConfig( strategy.clientId, strategy.clientSecret, strategy.redirectUri, wellKnownUri.provider ) await client.initOIDCConfig(wellKnownUri.url) return client } } ``` **Key Features**: - Strategy pattern for different providers - Supports: OBP-OIDC, Keycloak, Google, GitHub, etc. - Extensible for new providers #### 5.2.4 Session Helper (`src/lib/oauth/sessionHelper.ts`) ```typescript export class SessionOAuthHelper { static async handleCallback( session: Session, code: string, state: string, provider: string ): Promise { // Validate state // Exchange code for tokens // Store tokens in session // Fetch user info } static async refreshAccessToken(session: Session, provider: string): Promise { // Use refresh token to get new access token // Update session with new tokens } } ``` ### 5.3 OBP-Portal Configuration (`.env.example`) ```bash # OIDC Configuration OBP_OAUTH_CLIENT_ID=2a47cc56-0db1-409d-8f0b-131e4f94a212 OBP_OAUTH_CLIENT_SECRET=OR04Ga8uQjdzFmNXDI5QrEXHN30Fk4u6lbtbrrMsj8g OBP_OAUTH_WELL_KNOWN_URL=http://127.0.0.1:9000/obp-oidc/.well-known/openid-configuration APP_CALLBACK_URL=http://localhost:5174/login/obp/callback # OBP API Configuration PUBLIC_OBP_BASE_URL=http://localhost:8080 OBP_API_HOST=localhost:8080 OBP_API_URL=http://localhost:8080 # Redis Configuration REDIS_HOST=localhost REDIS_PORT=6379 ``` ### 5.4 OBP-Portal Login Flow ``` 1. User clicks "Login" → Navigate to /login/obp 2. Server-side: - Generate PKCE challenge/verifier - Generate state parameter - Store in session - Redirect to OBP-OIDC /auth endpoint 3. User authenticates on OBP-OIDC 4. OBP-OIDC redirects to /login/obp/callback?code=XXX&state=YYY 5. Server-side: - Validate state - Exchange code for tokens (with PKCE verifier) - Store tokens in session - Fetch user info from /userinfo - Store user info in session - Redirect to application 6. Client-side: Authenticated state ``` ### 5.5 OBP-Portal Hooks Implementation From `src/hooks.server.ts`: ```typescript // Initialize OAuth providers on startup await oauth2ProviderManager.start() // Check session validity for protected routes const checkSessionValidity: Handle = async ({ event, resolve }) => { const session = event.locals.session if (session.data.user) { // Check if access token is expired const isExpired = await checkAccessTokenExpiration(session.data.accessToken) if (isExpired && session.data.refreshToken) { // Attempt to refresh await SessionOAuthHelper.refreshAccessToken(session, session.data.provider) } } return resolve(event) } ``` --- ## 6. Implementation Phases ### Phase 1: Preparation & Setup (Week 1) **Goals**: Set up OBP-OIDC and understand integration requirements **Tasks**: 1. ✅ Set up OBP-OIDC server locally - Install dependencies (Java 11+, Maven, PostgreSQL) - Configure database views (see OBP-API scripts) - Copy `run-server.example.sh` to `run-server.sh` - Update database credentials - Start OIDC server: `./run-server.sh` - Verify: `curl http://localhost:9000/obp-oidc/.well-known/openid-configuration` 2. ✅ Register API Explorer II client in OBP-OIDC - Update `run-server.sh`: ```bash export OIDC_CLIENT_EXPLORER_ID=obp-explorer-ii-client export OIDC_CLIENT_EXPLORER_SECRET= export OIDC_CLIENT_EXPLORER_REDIRECTS=http://localhost:5173/login/obp/callback ``` - Restart OBP-OIDC - Copy client credentials 3. ✅ Study OBP-Portal implementation - Review OAuth2 client implementation - Understand provider manager pattern - Study session management approach - Document key differences from current implementation 4. ⬜ Add OAuth2 dependencies ```bash npm install arctic jsonwebtoken @types/jsonwebtoken ``` - `arctic`: Modern OAuth2/OIDC client library - `jsonwebtoken`: JWT parsing and validation **Deliverables**: - Running OBP-OIDC instance - API Explorer II client registered - Dependencies added to `package.json` - This preparation document --- ### Phase 2: Backend OAuth2 Implementation (Week 2-3) **Goals**: Implement OAuth2/OIDC backend while maintaining OAuth 1.0a **Tasks**: #### 2.1 Create OAuth2 Service Layer **File**: `server/services/OAuth2Service.ts` ```typescript import { OAuth2Client } from 'arctic' import { Service } from 'typedi' export interface OIDCConfiguration { issuer: string authorization_endpoint: string token_endpoint: string userinfo_endpoint: string jwks_uri: string } @Service() export class OAuth2Service { private client: OAuth2Client private oidcConfig: OIDCConfiguration | null = null constructor() { const clientId = process.env.VITE_OBP_OAUTH2_CLIENT_ID const clientSecret = process.env.VITE_OBP_OAUTH2_CLIENT_SECRET const redirectUri = process.env.VITE_OBP_OAUTH2_REDIRECT_URL this.client = new OAuth2Client(clientId, clientSecret, redirectUri) } async initializeFromWellKnown(wellKnownUrl: string): Promise { const response = await fetch(wellKnownUrl) this.oidcConfig = await response.json() } createAuthorizationURL(state: string, codeChallenge: string): URL { if (!this.oidcConfig) { throw new Error('OIDC configuration not initialized') } return this.client.createAuthorizationURL(this.oidcConfig.authorization_endpoint, state, [ 'openid', 'profile', 'email' ]) } async exchangeCodeForTokens(code: string, codeVerifier: string): Promise { if (!this.oidcConfig) { throw new Error('OIDC configuration not initialized') } return await this.client.validateAuthorizationCode( this.oidcConfig.token_endpoint, code, codeVerifier ) } async refreshAccessToken(refreshToken: string): Promise { // Implement refresh token flow } async getUserInfo(accessToken: string): Promise { if (!this.oidcConfig) { throw new Error('OIDC configuration not initialized') } const response = await fetch(this.oidcConfig.userinfo_endpoint, { headers: { Authorization: `Bearer ${accessToken}` } }) return await response.json() } } ``` #### 2.2 Create PKCE Utility **File**: `server/utils/pkce.ts` ```typescript import crypto from 'crypto' export class PKCEUtils { static generateCodeVerifier(): string { return crypto.randomBytes(32).toString('base64url') } static generateCodeChallenge(verifier: string): string { return crypto.createHash('sha256').update(verifier).digest('base64url') } static generateState(): string { return crypto.randomBytes(32).toString('hex') } } ``` #### 2.3 Create OAuth2 Middlewares **File**: `server/middlewares/OAuth2AuthorizationMiddleware.ts` ```typescript import { ExpressMiddlewareInterface } from 'routing-controllers' import { Request, Response } from 'express' import { Service } from 'typedi' import { OAuth2Service } from '../services/OAuth2Service' import { PKCEUtils } from '../utils/pkce' @Service() export default class OAuth2AuthorizationMiddleware implements ExpressMiddlewareInterface { constructor(private oauth2Service: OAuth2Service) {} async use(request: Request, response: Response): Promise { const session = request.session const redirectPage = request.query.redirect // Store redirect page in session if (redirectPage) { session['redirectPage'] = redirectPage } // Generate PKCE parameters const codeVerifier = PKCEUtils.generateCodeVerifier() const codeChallenge = PKCEUtils.generateCodeChallenge(codeVerifier) const state = PKCEUtils.generateState() // Store in session for callback validation session['oauth2_state'] = state session['oauth2_code_verifier'] = codeVerifier // Create authorization URL const authUrl = this.oauth2Service.createAuthorizationURL(state, codeChallenge) // Add PKCE challenge to URL authUrl.searchParams.set('code_challenge', codeChallenge) authUrl.searchParams.set('code_challenge_method', 'S256') console.log('Redirecting to OAuth2 authorization:', authUrl.toString()) response.redirect(authUrl.toString()) } } ``` **File**: `server/middlewares/OAuth2CallbackMiddleware.ts` ```typescript import { ExpressMiddlewareInterface } from 'routing-controllers' import { Request, Response } from 'express' import { Service } from 'typedi' import { OAuth2Service } from '../services/OAuth2Service' import jwt from 'jsonwebtoken' @Service() export default class OAuth2CallbackMiddleware implements ExpressMiddlewareInterface { constructor(private oauth2Service: OAuth2Service) {} async use(request: Request, response: Response): Promise { const session = request.session const code = request.query.code as string const state = request.query.state as string // Validate state const storedState = session['oauth2_state'] if (!state || state !== storedState) { console.error('State validation failed') return response.status(400).send('Invalid state parameter') } // Get code verifier from session const codeVerifier = session['oauth2_code_verifier'] if (!codeVerifier) { console.error('Code verifier not found in session') return response.status(400).send('Invalid session state') } try { // Exchange authorization code for tokens const tokens = await this.oauth2Service.exchangeCodeForTokens(code, codeVerifier) // Get user info const userInfo = await this.oauth2Service.getUserInfo(tokens.accessToken) // Store in session session['oauth2_access_token'] = tokens.accessToken session['oauth2_refresh_token'] = tokens.refreshToken session['oauth2_id_token'] = tokens.idToken session['oauth2_user_info'] = userInfo // Decode ID token for user info const idTokenPayload = jwt.decode(tokens.idToken) session['oauth2_user'] = { sub: idTokenPayload.sub, email: idTokenPayload.email, name: idTokenPayload.name, username: idTokenPayload.preferred_username || idTokenPayload.sub } // Clear OAuth2 flow parameters delete session['oauth2_state'] delete session['oauth2_code_verifier'] // Redirect to original page or home const redirectPage = session['redirectPage'] || process.env.VITE_OBP_API_EXPLORER_HOST delete session['redirectPage'] console.log('OAuth2 authentication successful, redirecting to:', redirectPage) response.redirect(redirectPage) } catch (error) { console.error('OAuth2 token exchange failed:', error) response.status(500).send('Authentication failed: ' + error.message) } } } ``` #### 2.4 Create OAuth2 Controllers **File**: `server/controllers/OAuth2ConnectController.ts` ```typescript import { Controller, Req, Res, Get, UseBefore } from 'routing-controllers' import { Request, Response } from 'express' import { Service } from 'typedi' import OAuth2AuthorizationMiddleware from '../middlewares/OAuth2AuthorizationMiddleware' @Service() @Controller() @UseBefore(OAuth2AuthorizationMiddleware) export class OAuth2ConnectController { @Get('/oauth2/connect') connect(@Req() request: Request, @Res() response: Response): Response { return response } } ``` **File**: `server/controllers/OAuth2CallbackController.ts` ```typescript import { Controller, Req, Res, Get, UseBefore } from 'routing-controllers' import { Request, Response } from 'express' import { Service } from 'typedi' import OAuth2CallbackMiddleware from '../middlewares/OAuth2CallbackMiddleware' @Service() @Controller() @UseBefore(OAuth2CallbackMiddleware) export class OAuth2CallbackController { @Get('/oauth2/callback') callback(@Req() request: Request, @Res() response: Response): Response { return response } } ``` #### 2.5 Update Server Application **File**: `server/app.ts` Add OAuth2 service initialization: ```typescript import { OAuth2Service } from './services/OAuth2Service' import Container from 'typedi' // Initialize OAuth2 service const oauth2Service = Container.get(OAuth2Service) const wellKnownUrl = process.env.VITE_OBP_OAUTH2_WELL_KNOWN_URL if (wellKnownUrl) { oauth2Service .initializeFromWellKnown(wellKnownUrl) .then(() => { console.log('OAuth2 service initialized from well-known URL:', wellKnownUrl) }) .catch((error) => { console.error('Failed to initialize OAuth2 service:', error) }) } ``` #### 2.6 Add Current User Endpoint for OAuth2 **File**: `server/controllers/UserController.ts` Update to support both OAuth 1.0a and OAuth2: ```typescript @Get('/user/current') async getCurrentUser(@Session() session: any, @Res() response: Response): Promise { // Check OAuth2 session first if (session['oauth2_user']) { return response.json({ username: session['oauth2_user'].username, email: session['oauth2_user'].email, name: session['oauth2_user'].name, provider: 'oauth2' }); } // Fall back to OAuth 1.0a (existing code) if (session['clientConfig']) { // ... existing OAuth 1.0a logic } return response.json({}); } ``` **Deliverables**: - OAuth2Service with OIDC discovery - PKCE utilities - OAuth2 middlewares (authorization + callback) - OAuth2 controllers - Updated user endpoint supporting both auth methods --- ### Phase 3: Environment Configuration (Week 3) **Goals**: Add OAuth2 configuration alongside OAuth 1.0a **File**: `env_ai` (update) Add new OAuth2 variables: ```bash ### OAuth 1.0a Configuration (Legacy) ### VITE_OBP_CONSUMER_KEY=0xzsimlrhdguiiuuj1ncykcxzjrogxibjff3dthl VITE_OBP_CONSUMER_SECRET=ikf5wykke1oonykb33kmx3deh5ukbdak44ieg1l5 VITE_OBP_REDIRECT_URL=http://localhost:5173/api/callback ### OAuth2/OIDC Configuration (New) ### # Set to 'true' to use OAuth2 instead of OAuth 1.0a VITE_USE_OAUTH2=false # OAuth2 Client Credentials (from OBP-OIDC) VITE_OBP_OAUTH2_CLIENT_ID=obp-explorer-ii-client VITE_OBP_OAUTH2_CLIENT_SECRET= VITE_OBP_OAUTH2_REDIRECT_URL=http://localhost:5173/oauth2/callback # OIDC Well-Known Configuration URL VITE_OBP_OAUTH2_WELL_KNOWN_URL=http://127.0.0.1:9000/obp-oidc/.well-known/openid-configuration # Optional: Direct OIDC endpoints (if not using well-known) # VITE_OBP_OAUTH2_AUTHORIZATION_URL=http://127.0.0.1:9000/obp-oidc/auth # VITE_OBP_OAUTH2_TOKEN_URL=http://127.0.0.1:9000/obp-oidc/token # VITE_OBP_OAUTH2_USERINFO_URL=http://127.0.0.1:9000/obp-oidc/userinfo ### API Configuration ### VITE_OBP_API_HOST=http://127.0.0.1:8080 VITE_OBP_API_VERSION=v5.1.0 VITE_OBP_API_EXPLORER_HOST=http://localhost:5173 ``` **Deliverables**: - Updated `env_ai` with OAuth2 configuration - Documentation on environment variables --- ### Phase 4: Frontend Updates (Week 4) **Goals**: Update frontend to support OAuth2 login flow #### 4.1 Update Login Links **File**: `src/components/HeaderNav.vue` Update login button to use OAuth2 when enabled: ```vue ``` #### 4.2 Update Logoff Handling **File**: `server/controllers/UserController.ts` ```typescript @Get('/user/logoff') async logoff(@Session() session: any, @Req() request: Request, @Res() response: Response): Promise { const redirectPage = request.query.redirect || process.env.VITE_OBP_API_EXPLORER_HOST; // Clear OAuth2 session delete session['oauth2_access_token']; delete session['oauth2_refresh_token']; delete session['oauth2_id_token']; delete session['oauth2_user_info']; delete session['oauth2_user']; // Clear OAuth 1.0a session (backward compatibility) delete session['clientConfig']; // Destroy session session.destroy((err: any) => { if (err) { console.error('Error destroying session:', err); } }); return response.redirect(redirectPage); } ``` #### 4.3 Add OAuth2 Status Indicator (Optional) Add visual indicator showing which auth method is active: ```vue OAuth2 ``` **Deliverables**: - Updated login/logoff buttons - OAuth2-aware user state management - Optional status indicators --- ### Phase 5: Testing (Week 5) **Goals**: Comprehensive testing of OAuth2 implementation #### 5.1 Unit Tests **File**: `server/test/OAuth2Service.test.ts` ```typescript import { OAuth2Service } from '../services/OAuth2Service' import { PKCEUtils } from '../utils/pkce' describe('OAuth2Service', () => { let service: OAuth2Service beforeEach(() => { service = new OAuth2Service() }) test('should initialize from well-known URL', async () => { await service.initializeFromWellKnown( 'http://localhost:9000/obp-oidc/.well-known/openid-configuration' ) // Assert configuration is loaded }) test('should create valid authorization URL', () => { const state = PKCEUtils.generateState() const codeChallenge = PKCEUtils.generateCodeChallenge('test-verifier') const authUrl = service.createAuthorizationURL(state, codeChallenge) expect(authUrl.searchParams.get('response_type')).toBe('code') expect(authUrl.searchParams.get('state')).toBe(state) }) }) ``` **File**: `server/test/pkce.test.ts` ```typescript import { PKCEUtils } from '../utils/pkce' describe('PKCEUtils', () => { test('should generate valid code verifier', () => { const verifier = PKCEUtils.generateCodeVerifier() expect(verifier).toHaveLength(43) // Base64url encoded 32 bytes }) test('should generate valid code challenge', () => { const verifier = 'test-verifier' const challenge = PKCEUtils.generateCodeChallenge(verifier) expect(challenge).toBeTruthy() expect(typeof challenge).toBe('string') }) test('should generate unique states', () => { const state1 = PKCEUtils.generateState() const state2 = PKCEUtils.generateState() expect(state1).not.toBe(state2) }) }) ``` #### 5.2 Integration Tests Create integration test suite: ```typescript import request from 'supertest' import { app } from '../app' describe('OAuth2 Integration Flow', () => { test('GET /oauth2/connect should redirect to OIDC provider', async () => { const response = await request(app).get('/oauth2/connect').expect(302) expect(response.header.location).toContain('obp-oidc/auth') expect(response.header.location).toContain('code_challenge') }) test('GET /oauth2/callback should exchange code for tokens', async () => { // Mock OIDC server responses // Test callback flow }) }) ``` #### 5.3 Manual Testing Checklist **OAuth2 Login Flow**: - [ ] User clicks login button - [ ] Redirected to OBP-OIDC login page - [ ] Enter valid credentials - [ ] Successfully redirected back to API Explorer - [ ] User info displayed in header - [ ] Session persists across page refreshes - [ ] Access token works for API calls **Token Refresh**: - [ ] Access token expires after configured time - [ ] Refresh token automatically obtains new access token - [ ] User remains logged in without re-authentication **Logout Flow**: - [ ] User clicks logout button - [ ] Session cleared - [ ] Redirected to appropriate page - [ ] Cannot access protected resources **Error Handling**: - [ ] Invalid authorization code - [ ] Expired authorization code - [ ] State parameter mismatch - [ ] OIDC provider unavailable - [ ] Network errors during token exchange #### 5.4 Security Testing - [ ] PKCE challenge/verifier validation - [ ] State parameter prevents CSRF attacks - [ ] Tokens stored securely (not in localStorage) - [ ] HTTPS required in production - [ ] Token expiration enforced - [ ] Refresh tokens rotated (if supported) **Deliverables**: - Unit test suite - Integration test suite - Manual testing report - Security audit report --- ### Phase 6: Documentation & Migration (Week 6) **Goals**: Document changes and prepare migration guide #### 6.1 Update README.md Add OAuth2 section: ````markdown ## Authentication API Explorer II supports two authentication methods: ### OAuth 1.0a (Legacy) Traditional OAuth 1.0a flow with OBP-API. **Configuration**: ```bash VITE_USE_OAUTH2=false VITE_OBP_CONSUMER_KEY= VITE_OBP_CONSUMER_SECRET= VITE_OBP_REDIRECT_URL=http://localhost:5173/api/callback ``` ```` ### OAuth2/OIDC (Recommended) Modern OAuth2 with OpenID Connect support. **Configuration**: ```bash VITE_USE_OAUTH2=true VITE_OBP_OAUTH2_CLIENT_ID=obp-explorer-ii-client VITE_OBP_OAUTH2_CLIENT_SECRET= VITE_OBP_OAUTH2_REDIRECT_URL=http://localhost:5173/oauth2/callback VITE_OBP_OAUTH2_WELL_KNOWN_URL=http://127.0.0.1:9000/obp-oidc/.well-known/openid-configuration ``` **Setting up OBP-OIDC**: 1. Clone and setup OBP-OIDC: ```bash cd ~/Documents/workspace_2024/OBP-OIDC cp run-server.example.sh run-server.sh # Edit run-server.sh with your database credentials ./run-server.sh ``` 2. Copy client credentials from OBP-OIDC startup output 3. Update API Explorer II `.env` file 4. Restart API Explorer II ```` #### 6.2 Create Migration Guide **File**: `docs/OAUTH2_MIGRATION_GUIDE.md` ```markdown # OAuth2 Migration Guide ## For Developers ### Prerequisites - OBP-OIDC server running (or other OIDC provider) - Client credentials (ID and Secret) - Database views configured in OBP database ### Step-by-Step Migration 1. **Update Dependencies** ```bash npm install arctic jsonwebtoken @types/jsonwebtoken ```` 2. **Configure Environment** Update `.env`: ```bash VITE_USE_OAUTH2=true VITE_OBP_OAUTH2_CLIENT_ID=obp-explorer-ii-client VITE_OBP_OAUTH2_CLIENT_SECRET= VITE_OBP_OAUTH2_REDIRECT_URL=http://localhost:5173/oauth2/callback VITE_OBP_OAUTH2_WELL_KNOWN_URL=http://127.0.0.1:9000/obp-oidc/.well-known/openid-configuration ``` 3. **Test Authentication** - Start server: `npm run dev` - Click login - Verify redirect to OIDC provider - Login with valid credentials - Verify successful callback 4. **Verify API Calls** - Test protected endpoints - Verify Bearer token in requests - Check token refresh on expiration ### Rollback Procedure If issues occur: 1. Set `VITE_USE_OAUTH2=false` 2. Restart server 3. System reverts to OAuth 1.0a ## For System Administrators ### Production Deployment 1. **Use Production OIDC Provider** - Don't use OBP-OIDC in production - Recommended: Keycloak, Auth0, Okta - Configure provider in OBP-API well-known endpoints 2. **Security Requirements** - HTTPS only for all endpoints - Secure Redis with password - Rotate client secrets regularly - Monitor for suspicious activity 3. **Configuration** ```bash VITE_USE_OAUTH2=true VITE_OBP_OAUTH2_CLIENT_ID= VITE_OBP_OAUTH2_CLIENT_SECRET= VITE_OBP_OAUTH2_REDIRECT_URL=https://explorer.yourdomain.com/oauth2/callback VITE_OBP_OAUTH2_WELL_KNOWN_URL=https://auth.yourdomain.com/.well-known/openid-configuration ``` 4. **Monitoring** - Track authentication success/failure rates - Monitor token refresh failures - Alert on OIDC provider downtime ```` #### 6.3 Create Troubleshooting Guide **File**: `docs/OAUTH2_TROUBLESHOOTING.md` ```markdown # OAuth2 Troubleshooting Guide ## Common Issues ### 1. "State validation failed" **Cause**: State parameter mismatch or session timeout **Solution**: - Clear browser cookies - Check Redis connection - Verify session configuration in `server/app.ts` ### 2. "Code verifier not found in session" **Cause**: Session not persisting between requests **Solution**: - Check Redis is running: `redis-cli ping` - Verify `VITE_OBP_REDIS_URL` in `.env` - Check session cookie settings (secure, httpOnly) ### 3. "Token request failed: 401" **Cause**: Client credentials invalid **Solution**: - Verify `VITE_OBP_OAUTH2_CLIENT_ID` and `VITE_OBP_OAUTH2_CLIENT_SECRET` - Check client is registered in OBP-OIDC - Verify redirect URI matches exactly ### 4. "OIDC configuration not initialized" **Cause**: Well-known URL not accessible **Solution**: - Check OBP-OIDC is running - Verify `VITE_OBP_OAUTH2_WELL_KNOWN_URL` - Test URL: `curl http://localhost:9000/obp-oidc/.well-known/openid-configuration` ### 5. Redirect Loop **Cause**: Cookie not being set or read **Solution**: - Check `X-Forwarded-Proto` header in nginx config - Set `app.set('trust proxy', 1)` in production - Verify cookie domain settings ## Debug Mode Enable detailed logging: ```bash DEBUG=express-session npm run dev ```` Check session data in Redis: ```bash redis-cli > KEYS sess:* > GET sess: ``` ## Testing OIDC Provider Test discovery: ```bash curl http://localhost:9000/obp-oidc/.well-known/openid-configuration ``` Test authorization (should return HTML login form): ```bash curl "http://localhost:9000/obp-oidc/auth?response_type=code&client_id=obp-explorer-ii-client&redirect_uri=http://localhost:5173/oauth2/callback&scope=openid&state=test123" ``` ```` **Deliverables**: - Updated README with OAuth2 documentation - Migration guide for developers - Troubleshooting guide - Admin deployment guide --- ## 7. Technical Requirements ### 7.1 System Requirements **Development**: - Node.js >= 16.14 - npm >= 8.0.0 - Redis >= 6.0 - PostgreSQL >= 12 (for OBP database) - Java 11+ (for OBP-OIDC) - Maven 3.6+ (for OBP-OIDC) **Production**: - Same as development, plus: - HTTPS/TLS certificates - Production-grade OIDC provider (Keycloak, Auth0, Okta) - Load balancer with session affinity - Monitoring and alerting ### 7.2 Dependencies **New NPM Packages**: ```json { "dependencies": { "arctic": "^1.0.0", "jsonwebtoken": "^9.0.2" }, "devDependencies": { "@types/jsonwebtoken": "^9.0.6" } } ```` **Existing Dependencies** (no changes): - express - express-session - connect-redis - redis - routing-controllers - typedi ### 7.3 Database Requirements **OBP Database Views** (required for OBP-OIDC): - `v_oidc_users` - User authentication - `v_oidc_clients` - Client registration - `v_oidc_admin_clients` - Client management See: `OBP-API/obp-api/src/main/scripts/sql/OIDC/` ### 7.4 Network Requirements **Development**: - API Explorer II: `http://localhost:5173` - OBP-API: `http://localhost:8080` - OBP-OIDC: `http://localhost:9000` - Redis: `localhost:6379` **Production**: - HTTPS required for all endpoints - CORS properly configured - Firewall rules for service communication --- ## 8. Configuration Changes ### 8.1 Environment Variables **New Variables**: ```bash # OAuth2 Feature Flag VITE_USE_OAUTH2=false # Set to 'true' to enable OAuth2 # OAuth2 Client Configuration VITE_OBP_OAUTH2_CLIENT_ID=obp-explorer-ii-client VITE_OBP_OAUTH2_CLIENT_SECRET= VITE_OBP_OAUTH2_REDIRECT_URL=http://localhost:5173/oauth2/callback # OIDC Provider Configuration VITE_OBP_OAUTH2_WELL_KNOWN_URL=http://127.0.0.1:9000/obp-oidc/.well-known/openid-configuration # Optional: Manual endpoint configuration (if not using well-known) VITE_OBP_OAUTH2_AUTHORIZATION_URL=http://127.0.0.1:9000/obp-oidc/auth VITE_OBP_OAUTH2_TOKEN_URL=http://127.0.0.1:9000/obp-oidc/token VITE_OBP_OAUTH2_USERINFO_URL=http://127.0.0.1:9000/obp-oidc/userinfo VITE_OBP_OAUTH2_JWKS_URL=http://127.0.0.1:9000/obp-oidc/jwks # Token Configuration VITE_OBP_OAUTH2_TOKEN_REFRESH_THRESHOLD=300 # Refresh 5 minutes before expiry ``` **Existing Variables** (keep unchanged): ```bash # OAuth 1.0a (Legacy) - Still needed for backward compatibility VITE_OBP_CONSUMER_KEY= VITE_OBP_CONSUMER_SECRET= VITE_OBP_REDIRECT_URL=http://localhost:5173/api/callback # API Configuration VITE_OBP_API_HOST=http://127.0.0.1:8080 VITE_OBP_API_VERSION=v5.1.0 VITE_OBP_API_EXPLORER_HOST=http://localhost:5173 # Redis Configuration VITE_OBP_REDIS_URL=redis://127.0.0.1:6379 VITE_OPB_SERVER_SESSION_PASSWORD= # Opey Configuration VITE_CHATBOT_ENABLED=true VITE_CHATBOT_URL=http://localhost:5000 ``` ### 8.2 Session Configuration No changes needed - existing Redis session store works with OAuth2. **Verify** in `server/app.ts`: ```typescript cookie: { httpOnly: true, secure: process.env.NODE_ENV === 'production', // HTTPS only in production maxAge: 24 * 60 * 60 * 1000 // 24 hours } ``` ### 8.3 OBP-OIDC Configuration **File**: `OBP-OIDC/run-server.sh` Update Explorer II client section: ```bash export OIDC_CLIENT_EXPLORER_ID=obp-explorer-ii-client export OIDC_CLIENT_EXPLORER_SECRET= export OIDC_CLIENT_EXPLORER_REDIRECTS=http://localhost:5173/oauth2/callback,https://production-domain.com/oauth2/callback ``` **Multiple Environments**: Add comma-separated redirect URIs for dev, staging, production. --- ## 9. Code Changes Required ### 9.1 File Structure **New Files**: ``` server/ ├── services/ │ └── OAuth2Service.ts # OAuth2/OIDC client service ├── middlewares/ │ ├── OAuth2AuthorizationMiddleware.ts # Authorization flow │ └── OAuth2CallbackMiddleware.ts # Callback handling ├── controllers/ │ ├── OAuth2ConnectController.ts # /oauth2/connect endpoint │ └── OAuth2CallbackController.ts # /oauth2/callback endpoint ├── utils/ │ └── pkce.ts # PKCE helper functions └── test/ ├── OAuth2Service.test.ts # Service tests └── pkce.test.ts # PKCE tests docs/ ├── OAUTH2_MIGRATION_GUIDE.md # Migration documentation └── OAUTH2_TROUBLESHOOTING.md # Troubleshooting guide ``` **Modified Files**: ``` server/ ├── app.ts # Initialize OAuth2Service └── controllers/ └── UserController.ts # Support both auth methods src/ └── components/ └── HeaderNav.vue # Dual login button support ``` **Unchanged Files** (keep for backward compatibility): ``` server/ ├── services/ │ ├── OauthInjectedService.ts # OAuth 1.0a service │ └── OBPClientService.ts # OBP API client ├── middlewares/ │ ├── OauthRequestTokenMiddleware.ts │ └── OauthAccessTokenMiddleware.ts └── controllers/ ├── ConnectController.ts # /api/connect endpoint └── CallbackController.ts # /api/callback endpoint ``` ### 9.2 Breaking Changes **None** - Implementation is additive: - OAuth 1.0a code remains functional - New OAuth2 code runs in parallel - Feature flag controls which is active - No breaking API changes ### 9.3 API Changes **New Endpoints**: - `GET /oauth2/connect?redirect=` - Start OAuth2 flow - `GET /oauth2/callback?code=&state=` - OAuth2 callback **Modified Endpoints**: - `GET /user/current` - Returns user info from OAuth2 or OAuth 1.0a session - `GET /user/logoff` - Clears both OAuth2 and OAuth 1.0a sessions **Unchanged Endpoints**: - `GET /api/connect` - OAuth 1.0a flow (still works) - `GET /api/callback` - OAuth 1.0a callback (still works) --- ## 10. Testing Strategy ### 10.1 Test Pyramid ``` /\ / \ / \ / E2E \ / Tests \ /----------\ / \ / Integration \ / Tests \ /------------------\ / \ / Unit Tests \ /________________________\ ``` ### 10.2 Unit Tests (Target: 80% coverage) **Files to Test**: - `OAuth2Service.ts` - `pkce.ts` - `OAuth2AuthorizationMiddleware.ts` - `OAuth2CallbackMiddleware.ts` **Test Cases**: - PKCE code verifier generation - PKCE code challenge generation - State parameter generation - OIDC configuration initialization - Authorization URL creation - Token exchange logic - Error handling ### 10.3 Integration Tests **Test Scenarios**: 1. Full OAuth2 authentication flow 2. Token refresh flow 3. Logout flow 4. Error cases (invalid code, state mismatch) 5. Session persistence 6. Concurrent user sessions ### 10.4 End-to-End Tests Use Playwright for browser automation: ```typescript test('OAuth2 login flow', async ({ page }) => { // Navigate to API Explorer await page.goto('http://localhost:5173') // Click login await page.click('#login') // Should redirect to OBP-OIDC await page.waitForURL(/obp-oidc\/auth/) // Fill login form await page.fill('[name="username"]', 'test@example.com') await page.fill('[name="password"]', 'password123') await page.click('[type="submit"]') // Should redirect back to Explorer await page.waitForURL('http://localhost:5173') // Verify logged in await expect(page.locator('.login-user')).toBeVisible() }) ``` ### 10.5 Security Tests **Test Cases**: - CSRF protection via state parameter - PKCE validation - Token expiration enforcement - Secure cookie attributes - HTTPS enforcement in production - XSS prevention in redirect URLs ### 10.6 Performance Tests **Metrics to Track**: - OAuth2 flow completion time (should be < 2 seconds) - Token refresh time (should be < 500ms) - Session lookup time (Redis) - Memory usage with multiple sessions ### 10.7 Compatibility Tests **Browsers**: - Chrome/Chromium - Firefox - Safari - Edge **Platforms**: - Linux - macOS - Windows **OIDC Providers**: - OBP-OIDC - Keycloak - (Future: Auth0, Okta, etc.) --- ## 11. Deployment Considerations ### 11.1 Phased Rollout Strategy **Phase 1: Internal Testing** - Deploy to development environment - Enable OAuth2 for internal team only - Collect feedback - Fix issues **Phase 2: Beta Testing** - Deploy to staging environment - Enable OAuth2 for beta users - Monitor metrics (login success rate, errors) - Refine implementation **Phase 3: Production Rollout** - Deploy to production - Keep OAuth 1.0a as default (`VITE_USE_OAUTH2=false`) - Allow users to opt-in to OAuth2 - Monitor metrics **Phase 4: Full Migration** - Make OAuth2 default (`VITE_USE_OAUTH2=true`) - Keep OAuth 1.0a as fallback - Announce deprecation timeline for OAuth 1.0a **Phase 5: OAuth 1.0a Removal** (6+ months later) - Remove OAuth 1.0a code - Remove oauth npm package dependency - Update documentation ### 11.2 Production Configuration **Nginx Example**: ```nginx server { listen 443 ssl http2; server_name explorer.yourdomain.com; ssl_certificate /etc/ssl/certs/explorer.crt; ssl_certificate_key /etc/ssl/private/explorer.key; # Frontend location / { root /var/www/api-explorer/dist; try_files $uri $uri/ /index.html; } # Backend API location /api { proxy_pass http://localhost:8085; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Important for secure cookies } # OAuth2 endpoints location /oauth2 { proxy_pass http://localhost:8085; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Important for secure cookies } } ``` ### 11.3 Environment-Specific Settings **Development**: ```bash VITE_USE_OAUTH2=true VITE_OBP_OAUTH2_CLIENT_ID=obp-explorer-ii-client-dev VITE_OBP_OAUTH2_REDIRECT_URL=http://localhost:5173/oauth2/callback VITE_OBP_OAUTH2_WELL_KNOWN_URL=http://localhost:9000/obp-oidc/.well-known/openid-configuration NODE_ENV=development ``` **Staging**: ```bash VITE_USE_OAUTH2=true VITE_OBP_OAUTH2_CLIENT_ID=obp-explorer-ii-client-staging VITE_OBP_OAUTH2_REDIRECT_URL=https://staging-explorer.yourdomain.com/oauth2/callback VITE_OBP_OAUTH2_WELL_KNOWN_URL=https://staging-auth.yourdomain.com/.well-known/openid-configuration NODE_ENV=production ``` **Production**: ```bash VITE_USE_OAUTH2=true VITE_OBP_OAUTH2_CLIENT_ID=obp-explorer-ii-client-prod VITE_OBP_OAUTH2_REDIRECT_URL=https://explorer.yourdomain.com/oauth2/callback VITE_OBP_OAUTH2_WELL_KNOWN_URL=https://auth.yourdomain.com/.well-known/openid-configuration NODE_ENV=production ``` ### 11.4 Monitoring & Alerting **Metrics to Monitor**: - OAuth2 login success rate - OAuth2 login failure rate (by error type) - Token refresh success rate - Token refresh failure rate - Average login duration - Session count (active users) - OIDC provider response times - Redis connection status **Alerts to Configure**: - OAuth2 failure rate > 5% - OIDC provider unreachable - Redis connection lost - Token refresh failures spike - Session store full **Logging**: ```typescript // Add structured logging logger.info('OAuth2 login attempt', { userId: session.oauth2_user?.sub, provider: 'obp-oidc', timestamp: new Date().toISOString() }) logger.error('OAuth2 token exchange failed', { error: error.message, code: authCode, provider: 'obp-oidc' }) ``` ### 11.5 Backup & Recovery **Session Backup** (Redis): ```bash # Enable RDB snapshots save 900 1 save 300 10 save 60 10000 # Enable AOF for durability appendonly yes appendfsync everysec ``` **Configuration Backup**: - Store client secrets in secure vault (HashiCorp Vault, AWS Secrets Manager) - Version control all configuration files - Document manual configuration steps --- ## 12. Rollback Plan ### 12.1 Immediate Rollback (< 5 minutes) **If OAuth2 causes critical issues**: 1. **Update environment variable**: ```bash export VITE_USE_OAUTH2=false ``` 2. **Restart application**: ```bash pm2 restart api-explorer # OR systemctl restart api-explorer ``` 3. **Verify**: - Login button points to `/api/connect` - OAuth 1.0a flow works - Existing sessions remain valid **No code changes needed** - feature flag handles rollback. ### 12.2 Partial Rollback (Specific Users) **If issues affect some users but not others**: 1. Keep OAuth2 enabled globally 2. Add user-specific override in session: ```typescript if (session.forceOAuth1) { // Use OAuth 1.0a for this user } ``` 3. Or implement A/B testing: ```typescript const useOAuth2 = (userId) => { return userId % 10 < 5 // 50% of users } ``` ### 12.3 Full Rollback (Remove OAuth2 Code) **If OAuth2 needs to be completely removed**: 1. Delete new files: ```bash rm server/services/OAuth2Service.ts rm server/middlewares/OAuth2*.ts rm server/controllers/OAuth2*.ts rm server/utils/pkce.ts ``` 2. Remove dependencies: ```bash npm uninstall arctic jsonwebtoken ``` 3. Revert modified files: ```bash git checkout server/app.ts git checkout server/controllers/UserController.ts git checkout src/components/HeaderNav.vue ``` 4. Remove environment variables from `.env` 5. Rebuild and deploy: ```bash npm run build npm run build-server # Deploy ``` ### 12.4 Data Migration Rollback **OAuth2 sessions → OAuth 1.0a sessions**: No data migration needed because: - OAuth 1.0a and OAuth2 use separate session keys - Rolling back doesn't affect OAuth 1.0a sessions - Users will need to re-authenticate (acceptable) ### 12.5 Communication Plan **If rollback is needed**: 1. **Internal notification** (immediate): - Alert development team - Document the issue - Perform rollback 2. **User notification** (if service interruption): - Status page update - Email to affected users - Estimated time to resolution 3. **Post-mortem** (after rollback): - Root cause analysis - Timeline of events - Lessons learned - Action items to prevent recurrence --- ## 13. References ### 13.1 OAuth2/OIDC Standards - **RFC 6749**: OAuth 2.0 Authorization Framework - https://datatracker.ietf.org/doc/html/rfc6749 - **RFC 7636**: Proof Key for Code Exchange (PKCE) - https://datatracker.ietf.org/doc/html/rfc7636 - **OpenID Connect Core 1.0** - https://openid.net/specs/openid-connect-core-1_0.html - **OpenID Connect Discovery 1.0** - https://openid.net/specs/openid-connect-discovery-1_0.html ### 13.2 Libraries & Tools **Arctic** (OAuth2/OIDC client): - Documentation: https://arctic.js.org/ - GitHub: https://github.com/pilcrowonpaper/arctic - Used by: OBP-Portal **jsonwebtoken** (JWT handling): - Documentation: https://github.com/auth0/node-jsonwebtoken - NPM: https://www.npmjs.com/package/jsonwebtoken **Express Session**: - Documentation: https://github.com/expressjs/session - Used for: Session management with Redis ### 13.3 OBP Projects **OBP-OIDC**: - Location: `~/Documents/workspace_2024/OBP-OIDC` - README: See project README.md for setup instructions - GitHub: (Internal repository) **OBP-Portal** (Reference Implementation): - Location: `~/Documents/workspace_2024/OBP-Portal` - Key files: - `src/lib/oauth/client.ts` - OAuth2 client wrapper - `src/lib/oauth/providerManager.ts` - Multi-provider support - `src/lib/oauth/providerFactory.ts` - Provider strategy pattern - `src/lib/oauth/sessionHelper.ts` - Session management - `src/hooks.server.ts` - Server initialization **OBP-API**: - Database scripts: `OBP-API/obp-api/src/main/scripts/sql/OIDC/` - Well-known endpoint: `/obp/v5.1.0/well-known` ### 13.4 Additional Resources **OIDC Debugger**: - https://oidcdebugger.com/ - Tool for testing OIDC flows **JWT.io**: - https://jwt.io/ - Tool for decoding and inspecting JWTs **OAuth 2.0 Playground**: - https://www.oauth.com/playground/ - Interactive OAuth2 flow demonstration ### 13.5 Security Best Practices **OWASP OAuth 2.0 Security**: - https://cheatsheetseries.owasp.org/cheatsheets/OAuth2_Cheatsheet.html **OAuth 2.0 Security Best Current Practice**: - https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics **Key Recommendations**: 1. Always use PKCE for authorization code flow 2. Use state parameter to prevent CSRF 3. Validate redirect URIs strictly 4. Use HTTPS in production 5. Store tokens securely (never in localStorage) 6. Implement token refresh before expiration 7. Use short-lived access tokens 8. Rotate refresh tokens when possible 9. Log authentication events for audit 10. Monitor for suspicious activity --- ## Appendix A: Quick Reference ### A.1 OAuth2 vs OAuth 1.0a Comparison | Feature | OAuth 1.0a | OAuth2/OIDC | | ---------------------- | ----------------------- | --------------------------- | | **Signature** | HMAC-SHA1 required | Bearer token (no signature) | | **Request Complexity** | Complex (6+ parameters) | Simple (2-3 parameters) | | **Token Types** | Access token + secret | Access + Refresh + ID token | | **Token Format** | Opaque string | JWT (structured) | | **User Info** | Separate API call | Embedded in ID token | | **Refresh** | Not supported | Built-in refresh token | | **Mobile Support** | Difficult | Native support | | **Browser Support** | Limited | Full support | | **Standard** | Deprecated | Current standard | ### A.2 Key Environment Variables ```bash # Feature Flag VITE_USE_OAUTH2=true|false # Client Credentials VITE_OBP_OAUTH2_CLIENT_ID= VITE_OBP_OAUTH2_CLIENT_SECRET= # Endpoints VITE_OBP_OAUTH2_REDIRECT_URL= VITE_OBP_OAUTH2_WELL_KNOWN_URL= # API Configuration VITE_OBP_API_HOST= VITE_OBP_API_EXPLORER_HOST= # Session VITE_OBP_REDIS_URL= VITE_OPB_SERVER_SESSION_PASSWORD= ``` ### A.3 Common Commands **Start OBP-OIDC**: ```bash cd ~/Documents/workspace_2024/OBP-OIDC ./run-server.sh ``` **Start API Explorer II**: ```bash cd ~/Documents/workspace_2024/API-Explorer-II npm run dev ``` **Test OIDC Discovery**: ```bash curl http://localhost:9000/obp-oidc/.well-known/openid-configuration | jq ``` **Check Redis**: ```bash redis-cli ping redis-cli KEYS "sess:*" ``` **Run Tests**: ```bash npm test # Unit tests npm run test:integration # Integration tests npx playwright test # E2E tests ``` ### A.4 Troubleshooting Checklist **Before Starting**: - [ ] OBP-OIDC is running - [ ] Redis is running - [ ] OBP-API is running - [ ] Database views are created - [ ] Client is registered in OBP-OIDC - [ ] Environment variables are set **If Login Fails**: - [ ] Check browser console for errors - [ ] Verify redirect URI matches exactly - [ ] Check session cookie is set - [ ] Verify OIDC provider is accessible - [ ] Check server logs for errors - [ ] Verify client credentials **If Tokens Fail**: - [ ] Check token expiration - [ ] Verify refresh token exists - [ ] Check token format (valid JWT) - [ ] Verify signing algorithm (RS256) - [ ] Check JWKS endpoint ### A.5 File Checklist **New Files to Create**: - [ ] `server/services/OAuth2Service.ts` - [ ] `server/utils/pkce.ts` - [ ] `server/middlewares/OAuth2AuthorizationMiddleware.ts` - [ ] `server/middlewares/OAuth2CallbackMiddleware.ts` - [ ] `server/controllers/OAuth2ConnectController.ts` - [ ] `server/controllers/OAuth2CallbackController.ts` - [ ] `server/test/OAuth2Service.test.ts` - [ ] `server/test/pkce.test.ts` - [ ] `docs/OAUTH2_MIGRATION_GUIDE.md` - [ ] `docs/OAUTH2_TROUBLESHOOTING.md` **Files to Modify**: - [ ] `server/app.ts` - [ ] `server/controllers/UserController.ts` - [ ] `src/components/HeaderNav.vue` - [ ] `package.json` - [ ] `env_ai` - [ ] `README.md` **Files to Keep (Don't Delete)**: - [ ] `server/services/OauthInjectedService.ts` - [ ] `server/services/OBPClientService.ts` - [ ] `server/middlewares/OauthRequestTokenMiddleware.ts` - [ ] `server/middlewares/OauthAccessTokenMiddleware.ts` - [ ] `server/controllers/ConnectController.ts` - [ ] `server/controllers/CallbackController.ts` --- ## Appendix B: Testing Scenarios ### B.1 Happy Path Testing 1. **New User Login** - User clicks login - Redirects to OBP-OIDC - User enters credentials - Successfully redirected back - User info displayed - Session persists 2. **Returning User** - User has valid session - Page refresh maintains login - API calls include Bearer token - Token auto-refreshes before expiry 3. **Logout** - User clicks logout - Session cleared - Redirected appropriately - Cannot access protected resources ### B.2 Error Scenarios 1. **Invalid Credentials** - User enters wrong password - OBP-OIDC shows error - User can retry 2. **State Mismatch** - Tampered state parameter - Shows error message - User can restart flow 3. **Expired Code** - Authorization code expires - Shows error message - User can retry login 4. **Network Errors** - OIDC provider unreachable - Shows user-friendly error - Option to retry 5. **Token Expired** - Access token expires - Refresh token used automatically - User stays logged in ### B.3 Edge Cases 1. **Concurrent Logins** - User logs in from multiple browsers - Each session independent - Logout affects only current session 2. **Session Timeout** - User inactive for extended period - Session expires - Prompted to log in again 3. **Browser Refresh During Flow** - User refreshes during authorization - Flow restarts gracefully - No error shown 4. **Multiple Redirect URIs** - Different environments - Correct URI used per environment - No cross-environment issues --- ## Appendix C: Production Readiness Checklist ### C.1 Security - [ ] HTTPS enabled for all endpoints - [ ] Client secret stored securely (vault/secrets manager) - [ ] Session secret is strong and rotated - [ ] PKCE enabled and enforced - [ ] State parameter validated - [ ] CSRF protection enabled - [ ] XSS protection in place - [ ] Redirect URI whitelist enforced - [ ] Token storage secure (httpOnly cookies) - [ ] Rate limiting implemented - [ ] Audit logging enabled ### C.2 Performance - [ ] Redis configured for high availability - [ ] Session expiration tuned - [ ] Token refresh threshold optimized - [ ] Connection pooling configured - [ ] Caching strategy implemented - [ ] Load testing completed - [ ] Performance benchmarks met ### C.3 Monitoring - [ ] Application monitoring (APM) - [ ] Log aggregation configured - [ ] Metrics dashboard created - [ ] Alerts configured - [ ] Health checks in place - [ ] Uptime monitoring enabled - [ ] Error tracking configured ### C.4 Documentation - [ ] API documentation updated - [ ] Admin guide created - [ ] User guide updated - [ ] Troubleshooting guide complete - [ ] Runbook for common issues - [ ] Architecture diagrams created - [ ] Configuration documented ### C.5 Operations - [ ] Deployment automation tested - [ ] Rollback procedure documented - [ ] Backup strategy implemented - [ ] Disaster recovery plan - [ ] On-call procedures defined - [ ] Incident response plan - [ ] Change management process ### C.6 Testing - [ ] Unit tests passing (>80% coverage) - [ ] Integration tests passing - [ ] E2E tests passing - [ ] Security tests completed - [ ] Performance tests passed - [ ] Load tests completed - [ ] Penetration testing done (if required) --- ## Document History | Version | Date | Author | Changes | | ------- | ---- | --------------- | ------------------------- | | 1.0 | 2024 | TESOBE Dev Team | Initial document creation | --- ## Contact & Support **For Technical Questions**: - Development Team: dev@tesobe.com - Internal Slack: #obp-development **For Security Issues**: - Security Team: security@tesobe.com - Follow responsible disclosure guidelines **For Production Issues**: - On-call Team: oncall@tesobe.com - Status Page: status.openbankproject.com --- **END OF DOCUMENT**