API-Explorer-II/OAUTH2-OIDC-INTEGRATION-PREP.md
simonredfern ba783c0f22 Add OAuth2/OIDC integration preparation documentation
- Add OAUTH2-README.md: Overview and navigation guide
- Add OAUTH2-QUICK-START.md: 15-minute setup guide with code examples
- Add OAUTH2-OIDC-INTEGRATION-PREP.md: Complete 60-page implementation guide

Documentation covers:
- 6-phase implementation plan (6 weeks)
- Integration with OBP-OIDC provider
- Reference implementation from OBP-Portal
- Complete code examples for all components
- Testing strategy and deployment guide
- Backward compatibility with OAuth 1.0a
2025-11-29 19:48:30 +01:00

59 KiB

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
  2. Current State Analysis
  3. Target Architecture
  4. OBP-OIDC Overview
  5. OBP-Portal Reference Implementation
  6. Implementation Phases
  7. Technical Requirements
  8. Configuration Changes
  9. Code Changes Required
  10. Testing Strategy
  11. Deployment Considerations
  12. Rollback Plan
  13. 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=<current_path>
  • 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

VITE_OBP_CONSUMER_KEY=<consumer_key>
VITE_OBP_CONSUMER_SECRET=<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:

# 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=<secure_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=<random_state>
  &code_challenge=<pkce_challenge>
  &code_challenge_method=S256

Token Exchange:

POST http://localhost:9000/obp-oidc/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(client_id:client_secret)>

grant_type=authorization_code
&code=<auth_code>
&redirect_uri=http://localhost:5173/login/obp/callback
&code_verifier=<pkce_verifier>

UserInfo:

GET http://localhost:9000/obp-oidc/userinfo
Authorization: Bearer <access_token>

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)

import { OAuth2Client } from 'arctic'

export class OAuth2ClientWithConfig extends OAuth2Client {
  OIDCConfig?: OpenIdConnectConfiguration

  async initOIDCConfig(OIDCConfigUrl: string): Promise<void> {
    // 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<TokenResponse> {
    // 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)

class OAuth2ProviderManager {
  async fetchWellKnownUris(): Promise<WellKnownUri[]> {
    // 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)

class OAuth2ProviderFactory {
  private strategies: Map<string, ProviderStrategy>

  async initializeProvider(wellKnownUri: WellKnownUri): Promise<OAuth2ClientWithConfig | null> {
    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)

export class SessionOAuthHelper {
  static async handleCallback(
    session: Session,
    code: string,
    state: string,
    provider: string
  ): Promise<void> {
    // Validate state
    // Exchange code for tokens
    // Store tokens in session
    // Fetch user info
  }

  static async refreshAccessToken(session: Session, provider: string): Promise<void> {
    // Use refresh token to get new access token
    // Update session with new tokens
  }
}

5.3 OBP-Portal Configuration (.env.example)

# 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:

// 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:
      export OIDC_CLIENT_EXPLORER_ID=obp-explorer-ii-client
      export OIDC_CLIENT_EXPLORER_SECRET=<generate_secure_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

    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

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<void> {
    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<TokenResponse> {
    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<TokenResponse> {
    // Implement refresh token flow
  }

  async getUserInfo(accessToken: string): Promise<UserInfo> {
    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

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

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<void> {
    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

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<void> {
    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

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

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:

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:

@Get('/user/current')
async getCurrentUser(@Session() session: any, @Res() response: Response): Promise<Response> {
  // 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:

### 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=<your_secure_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

File: src/components/HeaderNav.vue

Update login button to use OAuth2 when enabled:

<template>
  <a v-bind:href="loginUrl" v-show="isShowLoginButton" class="login-button router-link" id="login">
    {{ $t('header.login') }}
  </a>
</template>

<script setup lang="ts">
const useOAuth2 = import.meta.env.VITE_USE_OAUTH2 === 'true'
const loginUrl = computed(() => {
  const currentPath = getCurrentPath()
  return useOAuth2
    ? `/oauth2/connect?redirect=${encodeURIComponent(currentPath)}`
    : `/api/connect?redirect=${encodeURIComponent(currentPath)}`
})
</script>

4.2 Update Logoff Handling

File: server/controllers/UserController.ts

@Get('/user/logoff')
async logoff(@Session() session: any, @Req() request: Request, @Res() response: Response): Promise<Response> {
  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:

<!-- In HeaderNav.vue or a settings component -->
<el-tag v-if="import.meta.env.VITE_USE_OAUTH2 === 'true'" type="info" size="small">
  OAuth2
</el-tag>

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

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

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:

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:

## 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=<your_consumer_key>
VITE_OBP_CONSUMER_SECRET=<your_consumer_secret>
VITE_OBP_REDIRECT_URL=http://localhost:5173/api/callback
```

Modern OAuth2 with OpenID Connect support.

Configuration:

VITE_USE_OAUTH2=true
VITE_OBP_OAUTH2_CLIENT_ID=obp-explorer-ii-client
VITE_OBP_OAUTH2_CLIENT_SECRET=<your_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:

    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
  1. Configure Environment Update .env:

    VITE_USE_OAUTH2=true
    VITE_OBP_OAUTH2_CLIENT_ID=obp-explorer-ii-client
    VITE_OBP_OAUTH2_CLIENT_SECRET=<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
    
  2. Test Authentication

    • Start server: npm run dev
    • Click login
    • Verify redirect to OIDC provider
    • Login with valid credentials
    • Verify successful callback
  3. 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

    VITE_USE_OAUTH2=true
    VITE_OBP_OAUTH2_CLIENT_ID=<production_client_id>
    VITE_OBP_OAUTH2_CLIENT_SECRET=<production_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:

redis-cli
> KEYS sess:*
> GET sess:<session_id>

Testing OIDC Provider

Test discovery:

curl http://localhost:9000/obp-oidc/.well-known/openid-configuration

Test authorization (should return HTML login form):

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:

# 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=<secure_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):

# OAuth 1.0a (Legacy) - Still needed for backward compatibility
VITE_OBP_CONSUMER_KEY=<consumer_key>
VITE_OBP_CONSUMER_SECRET=<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=<session_secret>

# 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:

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:

export OIDC_CLIENT_EXPLORER_ID=obp-explorer-ii-client
export OIDC_CLIENT_EXPLORER_SECRET=<generate_secure_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=<url> - Start OAuth2 flow
  • GET /oauth2/callback?code=<code>&state=<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:

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:

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:

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:

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:

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:

// 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):

# 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:

    export VITE_USE_OAUTH2=false
    
  2. Restart application:

    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:

    if (session.forceOAuth1) {
      // Use OAuth 1.0a for this user
    }
    
  3. Or implement A/B testing:

    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:

    rm server/services/OAuth2Service.ts
    rm server/middlewares/OAuth2*.ts
    rm server/controllers/OAuth2*.ts
    rm server/utils/pkce.ts
    
  2. Remove dependencies:

    npm uninstall arctic jsonwebtoken
    
  3. Revert modified files:

    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:

    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

13.2 Libraries & Tools

Arctic (OAuth2/OIDC client):

jsonwebtoken (JWT handling):

Express Session:

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:

JWT.io:

OAuth 2.0 Playground:

13.5 Security Best Practices

OWASP OAuth 2.0 Security:

OAuth 2.0 Security Best Current Practice:

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

# Feature Flag
VITE_USE_OAUTH2=true|false

# Client Credentials
VITE_OBP_OAUTH2_CLIENT_ID=<client_id>
VITE_OBP_OAUTH2_CLIENT_SECRET=<client_secret>

# Endpoints
VITE_OBP_OAUTH2_REDIRECT_URL=<callback_url>
VITE_OBP_OAUTH2_WELL_KNOWN_URL=<discovery_url>

# API Configuration
VITE_OBP_API_HOST=<obp_api_url>
VITE_OBP_API_EXPLORER_HOST=<explorer_url>

# Session
VITE_OBP_REDIS_URL=<redis_connection_string>
VITE_OPB_SERVER_SESSION_PASSWORD=<session_secret>

A.3 Common Commands

Start OBP-OIDC:

cd ~/Documents/workspace_2024/OBP-OIDC
./run-server.sh

Start API Explorer II:

cd ~/Documents/workspace_2024/API-Explorer-II
npm run dev

Test OIDC Discovery:

curl http://localhost:9000/obp-oidc/.well-known/openid-configuration | jq

Check Redis:

redis-cli ping
redis-cli KEYS "sess:*"

Run Tests:

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:

For Security Issues:

For Production Issues:


END OF DOCUMENT