> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://cla-demo.postmansolutions.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://cla-demo.postmansolutions.com/_mcp/server.

# SaaS IDP Migration from Entra to PING HLD

## Executive Summary

This document outlines the high-level design for implementing **dual authentication provider support** in the web-documents-ai application. The application will support both **Microsoft Entra ID (formerly Azure AD)** and **PING Identity** as Identity Providers (IdP), with the provider selection based on deployment environment.

**Provider Assignment:**

* **Microsoft Entra ID**: dev, qa, uat, prd, sbx (standard enterprise environments)

* **PING Identity**: saas-npd, saas-prd (SaaS offering environments)

**Scope**: Frontend (React SPA) with conditional auth logic, Backend API with dual token validation, Infrastructure (Helm Charts, Kubernetes), and CI/CD pipelines.

**Key Principle**: Same codebase supports both authentication providers through runtime configuration.

---

## Table of Contents

1. [Current State Analysis](#current-state-analysis)

2. [Target State Architecture](#target-state-architecture)

3. [Authentication Flow Comparison](#authentication-flow-comparison)

4. [Code Changes](#code-changes)

5. [Infrastructure Changes](#infrastructure-changes)

6. [Terraform Requirements](#terraform-requirements)

7. [Configuration Management](#configuration-management)

8. [Testing Strategy](#testing-strategy)

9. [Migration Phases](#migration-phases)

10. [Rollback Strategy](#rollback-strategy)

11. [Dependencies and Prerequisites](#dependencies-and-prerequisites)

---

## 1. Current State Analysis

### Current Authentication Stack

**Frontend:**

* **Library**: `@azure/msal-browser` (v3.5.0) and `@azure/msal-react` (v2.0.7)

* **Authentication Flow**: OAuth 2.0 / OpenID Connect through Microsoft Entra ID

* **Token Management**: MSAL handles token acquisition, refresh, and caching

* **Key Components**:

\- `AuthProvider.tsx` - Wraps application with MSAL context

\- `api.ts` - HTTP interceptor for token injection

\- `Login.tsx` - Login page with MSAL authentication

**Configuration:**

```javascript

{

  clientId: REACT_APP_AZURE_AD_CLIENT_ID,

  authority: REACT_APP_AZURE_AD_AUTHORITY,

  redirectUri: '/',

  scopes: [

    "api://{APP_ID}/ClientDepartments.Read",

    "api://{APP_ID}/Documents.Write",

    // ... 22 custom scopes

  ]

}

```

**Infrastructure:**

* Deployed to Azure AKS (Kubernetes)

* Istio service mesh for ingress/routing

* Helm charts for deployment

* Azure Workload Identity for pod-level authentication

* Environment variables injected via `50-load-env.sh` at container startup

---

## 2. Target State Architecture

### Dual Authentication Provider Support

**Architecture Principle**: Single codebase with runtime provider selection based on environment configuration.

**Provider Selection Logic:**

```javascript

const authProvider = window.env.AUTH_PROVIDER; // 'entra' or 'ping'

  

if (authProvider === 'ping') {

  // Use PING Identity (OIDC)

  // Environments: saas-npd, saas-prd

} else {

  // Use Microsoft Entra (MSAL)

  // Environments: dev, qa, uat, prd, sbx

}

```

**Key Components:**

1. **Entra Authorization Server**: Issues tokens for standard environments (dev, qa, uat, prd, sbx)

2. **PING Authorization Server**: Issues tokens for SaaS environments (saas-npd, saas-prd)

3. **Frontend Application**: React SPA with conditional auth provider initialization

4. **Backend API**: Validates tokens from either Entra or PING based on issuer

5. **Ingress/Gateway**: Routes traffic (auth at application level)

### Architecture Diagram

```

                    ┌──────────────────────────────────────┐

                    │  web-documents-ai Application        │

                    │  (Single Codebase)                   │

                    └──────────────┬───────────────────────┘

                                   │

                    ┌──────────────▼───────────────────────┐

                    │  Auth Provider Factory               │

                    │  (Runtime Selection)                 │

                    └──────────┬──────────────┬────────────┘

                               │              │

        ┌──────────────────────┴──┐       ┌──▼────────────────────────┐

        │  Microsoft Entra ID     │       │  PING Identity            │

        │  (MSAL)                 │       │  (OIDC)                   │

        │                         │       │                           │

        │  Environments:          │       │  Environments:            │

        │  • dev                  │       │  • saas-npd               │

        │  • qa                   │       │  • saas-prd               │

        │  • uat                  │       │                           │

        │  • prd                  │       │                           │

        │  • sbx                  │       │                           │

        └─────────────────────────┘       └───────────────────────────┘

                    │                                 │

                    └─────────────┬───────────────────┘

                                  │

                          ┌───────▼────────┐

                          │   Backend API  │

                          │ (Dual Token    │

                          │  Validation)   │

                          └────────────────┘

                                  │

                          ┌───────▼────────┐

                          │   Azure AKS    │

                          │   (Kubernetes) │

                          │   + Istio      │

                          └────────────────┘

```

---

## 3. Authentication Flow Comparison

### Provider Detection Flow

1. Application loads and reads `AUTH_PROVIDER` environment variable

2. If `AUTH_PROVIDER === 'ping'`:

\- Initialize OIDC client for PING Identity

\- Use `oidc-client-ts` library

3. If `AUTH_PROVIDER === 'entra'` (or undefined/default):

\- Initialize MSAL client for Microsoft Entra

\- Use `@azure/msal-browser` library

4. Appropriate auth provider handles all authentication flows

### Microsoft Entra Flow (dev, qa, uat, prd, sbx)

1. User lands on `/login`

2. MSAL initiates OAuth 2.0 Authorization Code Flow with PKCE

3. User redirected to `login.microsoftonline.com`

4. After authentication, redirect to app with authorization code

5. MSAL exchanges code for access token

6. Token stored in browser localStorage

7. API requests include token in Authorization header

8. Backend validates token against Microsoft Entra public keys

### PING Identity Flow (saas-npd, saas-prd)

1. User lands on `/login`

2. OIDC client initiates Authorization Code Flow with PKCE

3. User redirected to PING authorization endpoint

4. After authentication, redirect to app with authorization code

5. Client exchanges code for ID token + access token

6. Tokens stored securely (sessionStorage)

7. API requests include access token in Authorization header

8. Backend validates token against PING's JWKS endpoint

**Key Differences:**

* Authorization endpoint: Microsoft vs PING

* Token validation endpoint: Different JWKS URLs

* Scope format: Azure AD format vs PING format

* Claims structure: Different token claims

---

## 4. Code Changes

### 4.1 Frontend Changes

#### Package Dependencies

**Keep (both providers needed):**

```json

{

  "@azure/msal-browser": "3.5.0",

  "@azure/msal-react": "2.0.7",

  "oidc-client-ts": "^3.0.1",

  "react-oidc-context": "^3.1.0",

  "jose": "^5.2.0"

}

```

**Rationale**: Application must support both MSAL (for Entra) and OIDC (for PING) simultaneously. Provider selection happens at runtime based on environment configuration.

#### AuthProvider.tsx Changes

**Current Structure:**

* `loadMSAL()` - Dynamic import of MSAL libraries

* `initializeMSAL()` - Creates `PublicClientApplication`

* `msalInstance` - Global MSAL instance

* `MSALAuthProvider` - React component wrapping `MsalProvider`

**Target Structure (Dual Provider):**

* `detectAuthProvider()` - Determines which provider to use based on ENV

* `EntraAuthProvider` - Wraps MSAL logic (existing code)

* `PingAuthProvider` - Wraps OIDC logic (new code)

* `AuthProviderFactory` - Conditionally returns correct provider

* `useAuth()` hook - Unified interface regardless of provider

**Key Implementation Pattern:**

```typescript

// Unified AuthProvider that delegates to correct implementation

export const AuthProvider = ({ children }) => {

  const authProvider = window.env.AUTH_PROVIDER || 'entra';

  if (authProvider === 'ping') {

    return <PingAuthProvider>{children}</PingAuthProvider>;

  } else {

    return <EntraAuthProvider>{children}</EntraAuthProvider>;

  }

};

  

// Both providers expose same interface via useAuth() hook:

// - tokens: { accessToken, idToken }

// - roles: string[]

// - user: User object

// - handleSignin()

// - handleSignout()

// - switchClient()

// - etc.

```

const activeAccount = msalInstance.getActiveAccount();

const authResult = await msalInstance.acquireTokenSilent(\{

scopes: scopes,

account: activeAccount

});

return authResult.accessToken;

}

```

  

**Target Token Acquisition:**

```

```typescript

const getToken = async () => {

  const auth = useAuth();

  if (!auth.user) {

    throw new NotLoggedInError("User not authenticated");

  }

  // Check if token is expiring soon

  if (isTokenExpiring(auth.user.access_token)) {

    await auth.signinSilent(); // Renew token

  }

  return auth.user.access_token;

}

```

#### Scope Management

**Current Scopes:**

```typescript

scopes: [

  "api://{AZURE_AD_CLI_APP_ID}/ClientDepartments.Read",

  "api://{AZURE_AD_CLI_APP_ID}/Documents.Write",

  // ... 22 custom scopes

]

```

**Target Scopes:**

PING Identity typically uses different scope patterns. Options:

1. **Standard OIDC Scopes**: `openid`, `profile`, `email`

2. **Custom API Scopes**: Defined in PING's resource server

\- Example: `documents:read`, `documents:write`, `clients:read`

3. **Role-Based Claims**: Map Azure AD roles to PING groups/roles

**Decision Required**: Must coordinate with PING administrator to define scope structure that matches application requirements.

#### New Files Required

1. **`/src/auth/OidcConfig.ts`**: Centralized OIDC configuration

2. **`/src/auth/OidcCallback.tsx`**: Handles OIDC callback after authentication

3. **`/src/auth/SilentRenew.tsx`**: Silent token renewal page (if needed)

#### Modified Files

1. **`/src/components/AuthProvider.tsx`**: Complete rewrite to use OIDC

2. **`/src/utilities/api.ts`**: Update token acquisition logic

3. **`/src/routes/Login.tsx`**: Replace MSAL login with OIDC

4. **`/src/components/Navigation.tsx`**: Update logout handling

5. **`/src/index.jsx`**: Wrap app with `AuthProvider` from `react-oidc-context`

### 4.2 Backend API Changes

**Note**: This section assumes a separate backend API (not shown in frontend code)

#### Token Validation

**Current (Microsoft Entra):**

* Validate JWT signature using Microsoft's JWKS endpoint

* Verify issuer: `https://sts.windows.net/{tenant-id}/`

* Verify audience: `{client-id}` or `api://{app-id}`

* Extract claims: `roles`, `scp` (scopes), `oid` (user ID)

**Target (PING):**

* Validate JWT signature using PING's JWKS endpoint

\- Example: `https://auth.pingone.com/{env-id}/as/jwks`

* Verify issuer: `https://auth.pingone.com/{env-id}/as`

* Verify audience: PING client ID or resource identifier

* Extract claims: Map PING claims to application roles

\- Common claims: `sub`, `email`, `name`, `groups`, `scope`

#### Middleware Changes

**Example (Node.js/Express):**

```javascript

// Before (Azure AD)

const passport = require('passport');

const BearerStrategy = require('passport-azure-ad').BearerStrategy;

  

passport.use(new BearerStrategy({

  identityMetadata: `https://login.microsoftonline.com/${tenantId}/v2.0/.well-known/openid-configuration`,

  clientID: clientId,

  audience: `api://${appId}`,

}, (token, done) => { /* ... */ }));

  

// After (PING)

const { expressjwt: jwt } = require('express-jwt');

const jwksRsa = require('jwks-rsa');

  

const jwtCheck = jwt({

  secret: jwksRsa.expressJwtSecret({

    jwksUri: `https://auth.pingone.com/${envId}/as/jwks`

  }),

  audience: pingClientId,

  issuer: `https://auth.pingone.com/${envId}/as`,

  algorithms: ['RS256']

});

  

app.use(jwtCheck);

```

**Example (Python/FastAPI):**

```python

# Before (Azure AD)

from fastapi_azure_auth import MultiTenantAzureAuthorizationCodeBearer

  

azure_scheme = MultiTenantAzureAuthorizationCodeBearer(

    app_client_id=app_client_id,

    tenant_id=tenant_id,

)

  

# After (PING)

from fastapi import Security

from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

from jose import jwt

  

security = HTTPBearer()

  

def verify_ping_token(credentials: HTTPAuthorizationCredentials = Security(security)):

    jwks = requests.get(f'https://auth.pingone.com/{env_id}/as/jwks').json()

    # Validate token using jwks

```

#### Role/Permission Mapping

Create a mapping table for Azure AD roles to PING groups:

\| Azure AD Role | PING Group/Role | Application Permission |

\|--------------|-----------------|------------------------|

\| Administrator | admin | Full access |

\| User | user | Read/Write invoices |

\| Developer | developer | Dev features |

\| Auditor | auditor | Read-only |

**Implementation Options:**

1. **Claims Mapping**: Map PING group claims to application roles

2. **Database Lookup**: Store user-role mapping in database

3. **PING Attributes**: Use PING user attributes for role assignment

### 4.3 Development Mode

**Current**: Mock MSAL tokens using `jose` library for local development

**Target**: Two options:

1. **Mock OIDC Tokens**: Similar approach with custom JWT generation

2. **PING Sandbox**: Use PING's sandbox environment for local testing

**Recommended**: Keep mock token approach for offline development:

```typescript

export let injectLocalDevelopmentTokens = async () => {

  const mockUser = {

    id_token: await createMockIdToken(),

    access_token: await createMockAccessToken(),

    profile: {

      sub: '00000000-0000-0000-0000-000000000000',

      name: 'CLA Developer',

      email: 'developer@claconnect.invalid',

    },

    expires_at: Date.now() + 7200000,

  };

  // Store in sessionStorage for OIDC client

  sessionStorage.setItem('oidc.user', JSON.stringify(mockUser));

};

```

---

## 5. Infrastructure Changes

### 5.1 Helm Chart Changes

#### Environment Variables

**File**: `helm/values-{env}.yaml`

**Remove:**

```yaml

env:

  REACT_APP_AZURE_AD_AUTHORITY: "https://login.microsoftonline.com/{tenant-id}"

  REACT_APP_AZURE_AD_CLIENT_ID: "{azure-client-id}"

  REACT_APP_AZURE_AD_CLI_CLIENT_APP_ID: "api://app-{app-id}/"

```

**Add:**

```yaml

env:

  PING_AUTHORITY: "https://auth.pingone.com/{environment-id}/as"

  PING_CLIENT_ID: "{ping-client-id}"

  PING_CLIENT_SECRET: ""  # Only if using confidential client (not typical for SPA)

  PING_ISSUER: "https://auth.pingone.com/{environment-id}/as"

  PING_JWKS_URI: "https://auth.pingone.com/{environment-id}/as/jwks"

  PING_SCOPE: "openid profile email documents:read documents:write"

```

**Note**: Each environment (dev, qa, uat, prd) will have different PING environment IDs.

#### Service Account Annotations

**Current:**

```yaml

serviceAccount:

  azureWorkloadIdentityClientId: "31f10a9c-d3c7-47d6-9cfb-536a1d9008f4"

```

**Status**: Azure Workload Identity may still be needed for backend services accessing Azure resources (Storage, Key Vault). PING handles user authentication only.

**If PING manages all auth**: This can be removed or adjusted based on backend needs.

#### Ingress/Istio Configuration

**Current**: Istio routes traffic to service, no auth at gateway level

**Options for PING Integration:**

1. **Application-Level Auth (Recommended)**:

\- Keep current setup

\- Application handles all OIDC flows

\- No changes to Istio configuration

2. **Gateway-Level Auth** (Advanced):

\- Configure Istio `RequestAuthentication` and `AuthorizationPolicy`

\- Validate JWT at gateway before routing to pods

\- Requires PING JWKS endpoint configuration

**Example Istio RequestAuthentication:**

```yaml

apiVersion: security.istio.io/v1beta1

kind: RequestAuthentication

metadata:

  name: ping-jwt-auth

  namespace: documents-ai

spec:

  selector:

    matchLabels:

      app: web-documents-ai

  jwtRules:

  - issuer: "https://auth.pingone.com/{env-id}/as"

    jwksUri: "https://auth.pingone.com/{env-id}/as/jwks"

    audiences:

    - "{ping-client-id}"

```

**Decision Required**: Determine if gateway-level validation is desired or if app-level is sufficient.

### 5.2 Container Changes

#### Dockerfile

**No changes required** - Authentication logic is in JavaScript, not Docker image.

#### Startup Script (50-load-env.sh)

**Current:**

```bash

echo "  REACT_APP_AZURE_AD_AUTHORITY: '$(echo $REACT_APP_AZURE_AD_AUTHORITY)'," >> env.js

echo "  REACT_APP_AZURE_AD_CLIENT_ID: '$(echo $REACT_APP_AZURE_AD_CLIENT_ID)'," >> env.js

echo "  REACT_APP_AZURE_AD_CLI_APP_ID: '$(echo $REACT_APP_AZURE_AD_CLI_CLIENT_APP_ID)'," >> env.js

```

**Target:**

```bash

echo "  PING_AUTHORITY: '$(echo $PING_AUTHORITY)'," >> env.js

echo "  PING_CLIENT_ID: '$(echo $PING_CLIENT_ID)'," >> env.js

echo "  PING_ISSUER: '$(echo $PING_ISSUER)'," >> env.js

echo "  PING_JWKS_URI: '$(echo $PING_JWKS_URI)'," >> env.js

echo "  PING_SCOPE: '$(echo $PING_SCOPE)'," >> env.js

```

### 5.3 Kubernetes Secrets

**Create secrets for PING configuration** (if not using ConfigMap):

```bash

kubectl create secret generic ping-oidc-config \

  --from-literal=authority="https://auth.pingone.com/{env-id}/as" \

  --from-literal=client-id="{client-id}" \

  --namespace=documents-ai

```

**Helm Chart Reference:**

```yaml

env:

  PING_AUTHORITY:

    valueFrom:

      secretKeyRef:

        name: ping-oidc-config

        key: authority

  PING_CLIENT_ID:

    valueFrom:

      secretKeyRef:

        name: ping-oidc-config

        key: client-id

```

---

## 6. Terraform Requirements

### 6.1 PING Identity Resources

**Assumption**: PING Identity is managed separately (not via Terraform) by identity team.

**If using PingOne Terraform Provider:**

```hcl

terraform {

  required_providers {

    pingone = {

      source  = "pingidentity/pingone"

      version = "~> 0.20"

    }

  }

}

  

provider "pingone" {

  client_id      = var.pingone_client_id

  client_secret  = var.pingone_client_secret

  environment_id = var.pingone_environment_id

  region         = "NA"

}

  

# Create application in PingOne

resource "pingone_application" "web_documents_ai" {

  environment_id = var.pingone_environment_id

  name          = "Web Documents AI - ${var.environment}"

  enabled       = true

  

  oidc_options {

    type                        = "SINGLE_PAGE_APP"

    grant_types                 = ["AUTHORIZATION_CODE"]

    response_types              = ["CODE"]

    pkce_enforcement            = "S256_REQUIRED"

    token_endpoint_authn_method = "NONE"  # SPA uses PKCE, not client secret

    redirect_uris = [

      "https://documentsai.${var.environment}.claitdev.com/callback",

      "https://documentsai.${var.environment}.claitdev.com/silent-renew",

    ]

    post_logout_redirect_uris = [

      "https://documentsai.${var.environment}.claitdev.com/login",

    ]

  }

}

  

# Create resource scopes

resource "pingone_resource" "documents_api" {

  environment_id = var.pingone_environment_id

  name          = "Documents API"

  audience = "https://api.${var.environment}.claitdev.com/documentsai"

  scopes = [

    { name = "documents:read", description = "Read documents" },

    { name = "documents:write", description = "Write documents" },

    { name = "clients:read", description = "Read clients" },

    { name = "clients:write", description = "Write clients" },

  ]

}

  

# Output configuration for Helm charts

output "ping_authority" {

  value = "https://auth.pingone.com/${var.pingone_environment_id}/as"

}

  

output "ping_client_id" {

  value     = pingone_application.web_documents_ai.id

  sensitive = true

}

```

### 6.2 Azure Resources

**No changes required** for existing Azure infrastructure:

* AKS cluster

* Container registry

* Virtual network

* Key Vault (if used)

**Azure AD App Registration**: Can be decommissioned after successful migration.

### 6.3 DNS & Networking

**Verify**: Application domain must be accessible from PING for redirect callbacks.

**Allowlist**: Ensure PING authorization server can reach:

* `https://documentsai.{env}.claitdev.com/callback`

* `https://documentsai.{env}.claitdev.com/silent-renew` (if using)

### 6.4 Terraform Modules to Update

**Likely files**:

* `terraform/environments/{env}/main.tf`

* `terraform/modules/aks/main.tf`

* `terraform/modules/app-registration/main.tf` (can be removed or kept for other services)

**New files**:

* `terraform/modules/pingone-application/main.tf`

* `terraform/modules/pingone-application/variables.tf`

* `terraform/modules/pingone-application/outputs.tf`

---

## 7. Configuration Management

### 7.1 Environment-Specific Configuration

\| Environment | Auth Provider | Configuration Source | Identity System |

\|-------------|---------------|---------------------|------------------|

\| DEV | Microsoft Entra | Azure AD Dev Tenant | Enterprise Identity |

\| QA | Microsoft Entra | Azure AD Dev Tenant | Enterprise Identity |

\| UAT | Microsoft Entra | Azure AD Prod Tenant | Enterprise Identity |

\| PRD | Microsoft Entra | Azure AD Prod Tenant | Enterprise Identity |

\| SBX | Microsoft Entra | Azure AD Dev Tenant | Enterprise Identity |

\| SAAS-NPD | PING Identity | PING Dev Environment | SaaS Customer Identity |

\| SAAS-PRD | PING Identity | PING Prod Environment | SaaS Customer Identity |

#### Environment Variables by Provider

**Microsoft Entra Environments (dev, qa, uat, prd, sbx):**

```bash

AUTH_PROVIDER=entra

REACT_APP_AZURE_AD_CLIENT_ID={client-id}

REACT_APP_AZURE_AD_AUTHORITY={authority-url}

REACT_APP_AZURE_AD_CLI_APP_ID={app-id}/

```

**PING Identity Environments (saas-npd, saas-prd):**

```bash

AUTH_PROVIDER=ping

PING_AUTHORITY=https://auth.pingone.com/{env-id}/as

PING_CLIENT_ID={ping-client-id}

PING_ISSUER=https://auth.pingone.com/{env-id}/as

PING_JWKS_URI=https://auth.pingone.com/{env-id}/as/jwks

PING_SCOPE="openid profile email documents:read documents:write"

```

**Options**:

1. **Azure Key Vault**: Store PING configuration as secrets

```bash

az keyvault secret set --vault-name "kv-docs-ai-dev" \

  --name "PING-CLIENT-ID" \

  --value "{client-id}"

```

2. **Kubernetes Secrets**: Store in cluster

```bash

kubectl create secret generic ping-config \

  --from-literal=authority="..." \

  --namespace=documents-ai

```

3. **External Secrets Operator**: Sync from Azure Key Vault to Kubernetes

```yaml

apiVersion: external-secrets.io/v1beta1

kind: ExternalSecret

metadata:

  name: ping-config

spec:

  secretStoreRef:

    name: azure-keyvault

  target:

    name: ping-config

  data:

  - secretKey: authority

    remoteRef:

      key: PING-AUTHORITY

  - secretKey: client-id

    remoteRef:

      key: PING-CLIENT-ID

```

**Recommendation**: Use External Secrets Operator for unified secret management.

### 7.3 CI/CD Pipeline Updates

**GitHub Actions**: `.github/workflows/build-deploy.yml`

**Remove:**

```yaml

env:

  REACT_APP_AZURE_AD_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }}

  REACT_APP_AZURE_AD_AUTHORITY: ${{ vars.AZURE_TENANT_ID }}

```

**Add:**

```yaml

env:

  PING_AUTHORITY: ${{ vars.PING_AUTHORITY }}

  PING_CLIENT_ID: ${{ secrets.PING_CLIENT_ID }}

  PING_ISSUER: ${{ vars.PING_ISSUER }}

```

**Repository Secrets/Variables to Create:**

* `PING_AUTHORITY` (variable)

* `PING_CLIENT_ID` (secret)

* `PING_ISSUER` (variable)

* `PING_JWKS_URI` (variable)

**Per-Environment Secrets**: Use GitHub Environments for env-specific configuration.

---

## 8. Testing Strategy

### 8.1 Unit Tests

**Dual Provider Mock Strategy:**

Test files must mock both authentication providers since the application supports both:

**Entra Provider Mock:**

```typescript

jest.mock('@azure/msal-react', () => ({

  useMsal: () => ({

    instance: {

      loginRedirect: jest.fn(),

      logoutRedirect: jest.fn(),

      acquireTokenSilent: jest.fn().mockResolvedValue({

        accessToken: 'mock_entra_token',

      }),

    },

    accounts: [{ username: 'test@enterprise.com', name: 'Enterprise User' }],

  }),

  MsalProvider: ({ children }: any) => children,

}));

```

**PING Provider Mock:**

```typescript

jest.mock('react-oidc-context', () => ({

  useAuth: () => ({

    isAuthenticated: true,

    user: {

      access_token: 'mock_ping_token',

      profile: { sub: '123', name: 'SaaS User', email: 'user@saas.com' },

    },

    signinRedirect: jest.fn(),

    signoutRedirect: jest.fn(),

  }),

  AuthProvider: ({ children }: any) => children,

}));

```

**Provider Factory Tests:**

```typescript

describe('AuthProviderFactory', () => {

  it('should return EntraAuthProvider when AUTH_PROVIDER=entra', () => {

    process.env.REACT_APP_AUTH_PROVIDER = 'entra';

    const provider = getAuthProvider();

    expect(provider).toBeInstanceOf(EntraAuthProvider);

  });

  

  it('should return PingAuthProvider when AUTH_PROVIDER=ping', () => {

    process.env.REACT_APP_AUTH_PROVIDER = 'ping';

    const provider = getAuthProvider();

    expect(provider).toBeInstanceOf(PingAuthProvider);

  });

  

  it('should default to EntraAuthProvider when AUTH_PROVIDER not set', () => {

    delete process.env.REACT_APP_AUTH_PROVIDER;

    const provider = getAuthProvider();

    expect(provider).toBeInstanceOf(EntraAuthProvider);

  });

});

```

**Test Coverage Required:**

* AuthProvider factory initialization (both providers)

* Login/logout flows (both providers)

* Token refresh logic (both providers)

* API interceptor with tokens from both providers

* Role-based access control with both token formats

* useAuth() hook abstraction works with both providers

### 8.2 Integration Tests

**Test Both Providers in Respective Environments:**

**Entra Environments (dev, qa, uat, prd, sbx):**

1. **Login Flow**:

\- User clicks login → redirected to Microsoft login.microsoftonline.com

\- User authenticates → redirected back with code

\- App exchanges code for tokens (MSAL handles this)

\- User lands on dashboard

2. **Token Refresh**:

\- Token expires while user is active

\- MSAL silent token renewal succeeds

\- User continues session without interruption

3. **Logout Flow**:

\- User clicks logout

\- Redirected to Microsoft logout endpoint

\- Redirected back to login page

\- All tokens cleared from localStorage

4. **API Authorization**:

\- API call with valid Entra token → success

\- API call with expired token → MSAL refresh → retry → success

\- API call with invalid token → redirect to login

**PING Environments (saas-npd, saas-prd):**

1. **Login Flow**:

\- User clicks login → redirected to PING auth.pingone.com

\- User authenticates → redirected back with code

\- App exchanges code for tokens at /api/oidc/callback

\- User lands on dashboard

2. **Token Refresh**:

\- Token expires while user is active

\- oidc-client-ts silent refresh succeeds

\- User continues session without interruption

3. **Logout Flow**:

\- User clicks logout

\- Redirected to PING logout endpoint (RP-initiated logout)

\- Redirected back to login page

\- All tokens cleared from sessionStorage

4. **API Authorization**:

\- API call with valid PING token → success

\- API call with expired token → OIDC refresh → retry → success

\- API call with invalid token → redirect to login

**Common Tests (Both Providers):**

5. **Role-Based Access**:

\- Admin user can access admin features

\- Regular user cannot access admin features

\- Permissions enforced on backend

\- Role claims from both providers map correctly

6. **Multi-Tab Behavior**:

\- Login in one tab → other tabs detect authentication

\- Logout in one tab → other tabs detect logout

\- Works with both providers' storage mechanisms

7. **Deep Link Handling**:

\- Unauthenticated user visits protected route

\- Redirected to login (provider-specific)

\- After auth, redirected back to original route

### 8.3 End-to-End Tests

**Using Playwright or Cypress - Environment-Specific Tests:**

**Entra Environments Test:**

```typescript

test('Enterprise user can login with Entra', async ({ page }) => {

  // Test against dev/qa/uat/prd/sbx environments

  await page.goto('https://documentsai.dev.claitdev.com');

  await page.click('text=Login');

  // Wait for Microsoft login page

  await page.waitForURL(/login.microsoftonline.com/);

  // Enter credentials

  await page.fill('input[name="loginfmt"]', enterpriseUser.email);

  await page.click('text=Next');

  await page.fill('input[name="passwd"]', enterpriseUser.password);

  await page.click('input[type="submit"]');

  // Handle MFA if required

  // await page.click('text=Verify');

  // Should redirect back to app

  await page.waitForURL('https://documentsai.dev.claitdev.com/');

  // User should be logged in

  await expect(page.locator('text=Dashboard')).toBeVisible();

  await expect(page.locator(`text=${enterpriseUser.name}`)).toBeVisible();

});

```

**PING Environments Test:**

```typescript

test('SaaS user can login with PING', async ({ page }) => {

  // Test against saas-npd/saas-prd environments

  await page.goto('https://documentsai-saas.prd.claitdev.com');

  await page.click('text=Login');

  // Wait for PING login page

  await page.waitForURL(/auth.pingone.com/);

  // Enter credentials

  await page.fill('input[name="username"]', saasUser.username);

  await page.fill('input[name="password"]', saasUser.password);

  await page.click('button[type="submit"]');

  // Should redirect back to app via /api/oidc/callback

  await page.waitForURL('https://documentsai-saas.prd.claitdev.com/');

  // User should be logged in

  await expect(page.locator('text=Dashboard')).toBeVisible();

  await expect(page.locator(`text=${saasUser.name}`)).toBeVisible();

});

```

**Provider-Agnostic Tests (Run Against Both):**

```typescript

test.describe('Common Authentication Flows', () => {

  // Run this suite against both Entra and PING environments

  const environments = [

    { url: 'https://documentsai.dev.claitdev.com', provider: 'entra' },

    { url: 'https://documentsai-saas.prd.claitdev.com', provider: 'ping' },

  ];

  

  environments.forEach(({ url, provider }) => {

    test(`${provider}: Protected routes redirect to login`, async ({ page }) => {

      await page.goto(`${url}/dashboard`);

      // Should redirect to provider-specific login

      if (provider === 'entra') {

        await page.waitForURL(/login.microsoftonline.com/);

      } else {

        await page.waitForURL(/auth.pingone.com/);

      }

    });

  

    test(`${provider}: User can logout successfully`, async ({ page, context }) => {

      // Assume user is logged in

      await loginAs(page, provider);

      await page.goto(url);

      await page.click('text=Logout');

      // Should redirect to login page of respective provider

      await page.waitForURL(new RegExp(url));

      await expect(page.locator('text=Login')).toBeVisible();

      // Verify localStorage/sessionStorage is cleared

      const storage = await context.storageState();

      const hasAuthTokens = storage.cookies.some(c =>

        c.name.includes('msal') || c.name.includes('oidc')

      );

      expect(hasAuthTokens).toBe(false);

    });

  });

});

```

**Cross-Provider Validation:**

```typescript

test('Entra token cannot access PING environment', async ({ request }) => {

  const entraToken = await getEntraToken();

  const response = await request.get(

    'https://documentsai-saas.prd.claitdev.com/api/invoices',

    {

      headers: { Authorization: `Bearer ${entraToken}` }

    }

  );

  expect(response.status()).toBe(401); // Unauthorized

});

  

test('PING token cannot access Entra environment', async ({ request }) => {

  const pingToken = await getPingToken();

  const response = await request.get(

    'https://documentsai.dev.claitdev.com/api/invoices',

    {

      headers: { Authorization: `Bearer ${pingToken}` }

    }

  );

  expect(response.status()).toBe(401); // Unauthorized

});

```

### 8.4 Security Testing

**Validation Points for Both Providers:**

1. **JWT Signature**:

\- Backend properly validates Entra token signatures (Microsoft JWKS)

\- Backend properly validates PING token signatures (PING JWKS)

\- Invalid signatures are rejected from both providers

2. **Issuer Check**:

\- Backend rejects tokens from wrong Entra issuer

\- Backend rejects tokens from wrong PING issuer

\- Cross-provider tokens are rejected (Entra token to PING env, vice versa)

3. **Audience Check**:

\- Backend rejects Entra tokens for different audience

\- Backend rejects PING tokens for different audience/client\_id

4. **Expiration**:

\- Expired tokens from both providers are rejected

\- Backend does not accept tokens with exp in the past

5. **Scope Check**:

\- API endpoints enforce required scopes from Entra tokens

\- API endpoints enforce required scopes from PING tokens

\- Scope format differences handled correctly (Entra: `api://id/scope`, PING: `scope`)

6. **PKCE Flow**:

\- Entra: Authorization Code Flow uses PKCE (MSAL handles this)

\- PING: Authorization Code Flow uses PKCE (oidc-client-ts configured with PKCE)

\- Both prevent authorization code interception

7. **HTTPS Only**:

\- All OIDC flows use HTTPS for both providers

\- No sensitive data over HTTP

8. **Token Storage**:

\- Entra: Tokens stored in localStorage (MSAL default)

\- PING: Tokens stored in sessionStorage (oidc-client-ts config)

\- Tokens not exposed in logs or browser history

\- Console.log statements do not print tokens in production

9. **CORS Configuration**:

\- Backend allows requests from authorized origins only

\- Provider-specific redirect URIs whitelisted

10. **Environment Isolation**:

\- Entra app registrations scoped to enterprise environments

\- PING app registrations scoped to SaaS environments

\- No token cross-contamination

### 8.5 Performance Testing

**Metrics to Monitor (Per Provider):**

**Entra Environments (dev, qa, uat, prd, sbx):**

* Login time (Microsoft redirect → callback → MSAL token exchange)

* Token refresh time (MSAL silent token renewal)

* API latency (with Entra token validation via Microsoft JWKS)

* Concurrent user load (existing baseline)

* localStorage performance (MSAL cache)

**PING Environments (saas-npd, saas-prd):**

* Login time (PING redirect → callback → token exchange)

* Token refresh time (oidc-client-ts silent renewal)

* API latency (with PING token validation via PING JWKS)

* Concurrent user load (new SaaS customers)

* sessionStorage performance (OIDC cache)

**Comparative Analysis:**

* Compare PING performance to existing Entra baseline

* Ensure PING login time \<= Entra login time + 200ms

* Ensure PING token validation latency \<= Entra validation latency + 50ms

* Monitor network calls (PING may have different redirect patterns)

**Load Testing Scenarios:**

* 100 concurrent Entra users (enterprise employees)

* 100 concurrent PING users (SaaS customers)

* Mixed load: 50 Entra + 50 PING users (validate backend dual JWKS caching)

**Performance Targets:**

* Login flow: \< 2 seconds (from click to dashboard)

* Token refresh: \< 500ms (silent renewal)

* API call overhead: \< 100ms (token validation)

* Provider factory overhead: \< 10ms (conditional initialization)

---

## 9. Migration Phases

### Phase 0: Prerequisites (Week 1-2)

**Identity Team**:

* [ ] Set up PING Identity environments (saas-npd, saas-prd ONLY)

* [ ] Create OIDC application registrations in PING for SaaS environments

* [ ] Define scopes and claims structure for PING

* [ ] Configure redirect URIs for saas-npd and saas-prd

* [ ] Set up user provisioning for SaaS customers to PING

* [ ] Configure groups/roles in PING

* [ ] Provide OIDC configuration details to dev team

**Development Team**:

* [ ] Design dual provider architecture

* [ ] Create provider factory pattern

* [ ] Review PING documentation and samples

* [ ] Set up PING sandbox for testing

* [ ] Create technical design documents

* [ ] Plan backward compatibility for existing Entra environments

**Infrastructure Team**:

* [ ] Review current Helm charts

* [ ] Plan conditional configuration strategy

* [ ] Prepare SaaS-specific configurations

* [ ] Set up PING access from AKS (firewall rules for saas environments)

### Phase 1: Development - Dual Provider Support (Week 3-5)

**Goal**: Implement dual provider support without breaking existing environments

**Tasks**:

* [ ] Add oidc-client-ts and react-oidc-context dependencies (keep MSAL)

* [ ] Create PingAuthProvider component

* [ ] Create EntraAuthProvider component (refactor existing)

* [ ] Implement AuthProviderFactory with conditional logic

* [ ] Ensure useAuth() hook works with both providers

* [ ] Update api.ts for dual token handling

* [ ] Create OIDC callback page (for PING only)

* [ ] Add AUTH\_PROVIDER environment variable support

* [ ] Update all Helm values files (add PING config for saas-\* only)

* [ ] Update 50-load-env.sh for conditional variables

* [ ] Test in local dev with both providers

**Success Criteria**:

* [ ] Can switch between providers via AUTH\_PROVIDER env var

* [ ] Existing Entra environments (dev, qa, uat, prd, sbx) still work

* [ ] PING provider works in local testing

* [ ] All unit tests pass

* [ ] No breaking changes to existing code

### Phase 2: Backend Dual Token Validation (Week 6)

**Goal**: Backend accepts and validates tokens from both Entra and PING

**Tasks**:

* [ ] Update backend token validation middleware

* [ ] Support multiple JWKS endpoints (Entra + PING)

* [ ] Detect token issuer and route to correct validation

* [ ] Map both Entra and PING claims to application roles

* [ ] Unit tests for both token types

* [ ] Integration tests with both token types

* [ ] Deploy backend to DEV (still using Entra)

* [ ] Verify backward compatibility

**Success Criteria**:

* [ ] Backend accepts Entra tokens (existing behavior)

* [ ] Backend accepts PING tokens (new capability)

* [ ] Backend rejects invalid tokens from both sources

* [ ] Role-based access control works with both providers

* [ ] All API endpoints function correctly with both token types

### Phase 3: SAAS-NPD Environment (Week 7-8)

**Goal**: Deploy to SAAS-NPD with PING authentication

**Tasks**:

* [ ] Create Terraform for PING application in saas-npd

* [ ] Update Helm values-saas-npd.yaml with PING configuration

* [ ] Set AUTH\_PROVIDER=ping for saas-npd

* [ ] Update GitHub Actions for saas-npd deployment

* [ ] Deploy frontend and backend to saas-npd

* [ ] End-to-end testing with PING authentication

* [ ] QA team testing

* [ ] Bug fixes and refinements

**Success Criteria**:

* [ ] Users can login via PING in saas-npd

* [ ] API calls succeed with PING tokens

* [ ] Token refresh works

* [ ] Logout works

* [ ] All functional tests pass

* [ ] **ALL OTHER ENVIRONMENTS (dev, qa, uat, prd, sbx) STILL USE ENTRA**

### Phase 4: SAAS-PRD Environment (Week 9)

**Goal**: Deploy to SAAS-PRD for SaaS customers

**Tasks**:

* [ ] Create Terraform for PING application in saas-prd

* [ ] Update Helm values-saas-prd.yaml with PING configuration

* [ ] Set AUTH\_PROVIDER=ping for saas-prd

* [ ] Deploy to saas-prd

* [ ] Stakeholder testing

* [ ] SaaS customer UAT

* [ ] Final bug fixes

* [ ] Production runbook for dual-provider architecture

**Success Criteria**:

* [ ] Stakeholder sign-off

* [ ] SaaS customers can authenticate via PING

* [ ] No issues reported in production

* [ ] All environments operational (5 Entra + 2 PING)

### Phase 5: Post-Deployment Monitoring (Week 10+)

**Goal**: Ensure stability across all environments

**Tasks**:

* [ ] Monitor authentication metrics for all 7 environments

* [ ] Address any environment-specific issues

* [ ] Performance optimization (if needed)

* [ ] Update documentation

* [ ] Team training on dual-provider architecture

* [ ] Post-mortem / lessons learned

**Success Criteria**:

* [ ] No authentication-related incidents in any environment

* [ ] Both authentication providers performing well

* [ ] User satisfaction maintained across all environments

* [ ] Clear runbooks for supporting both providers

---

## 10. Rollback Strategy

### 10.1 Rollback Triggers

**When to rollback:**

* Critical authentication failures (>5% of login attempts)

* Token validation errors (>10% of API requests)

* Unable to login for >10 minutes after deployment

* Data access issues related to authorization

* Security vulnerability discovered in OIDC implementation

### 10.2 Rollback Procedure

**Quick Rollback (5-10 minutes):**

1. **Revert Helm Deployment**:

```bash

helm rollback web-documents-ai -n documents-ai

```

2. **Revert Backend Deployment** (if separate):

```bash

kubectl rollout undo deployment/api-documents-ai -n documents-ai

```

3. **Verify Rollback**:

\- Check pod status

\- Test login with Azure AD

\- Smoke test critical workflows

**Full Rollback (30-60 minutes):**

If Helm rollback insufficient:

1. **Checkout Previous Git Commit**:

```bash

git checkout <previous-release-tag>

```

2. **Rebuild and Deploy**:

\- Trigger CI/CD pipeline for previous version

\- Deploy to production

3. **Restore Configuration**:

\- Revert environment variables to Azure AD settings

\- Restore secrets if changed

4. **Verify Functionality**:

\- End-to-end testing

\- User verification

### 10.3 Data Considerations

**User sessions**: Users logged in during deployment will need to re-login. This is expected.

**Audit logs**: Ensure audit logs capture the migration event and any rollback.

**User preferences**: If stored in database, no data loss expected.

### 10.4 Communication During Rollback

**Internal**:

* Notify engineering team via Slack/Teams

* Update status page (if exists)

* Document rollback reason in incident log

**External**:

* Email/notification to users: "We experienced a technical issue and have restored service. Please log in again."

---

## 11. Dependencies and Prerequisites

### 11.1 Required Access

**PING Identity**:

* [ ] Admin access to PING environments

* [ ] Ability to create OIDC applications

* [ ] Ability to configure scopes and claims

* [ ] Access to PING documentation and support

**Azure**:

* [ ] AKS cluster admin access

* [ ] Container registry push access

* [ ] Key Vault read/write access (if used)

* [ ] DNS management (for redirect URIs)

**GitHub**:

* [ ] Repository admin access

* [ ] Ability to create secrets and variables

* [ ] Ability to modify workflows

### 11.2 External Dependencies

**PING Identity Platform**:

* PING availability and uptime

* PING API rate limits

* PING support for troubleshooting

**Network Connectivity**:

* Outbound connectivity from AKS to PING authorization server

* Inbound connectivity for PING to reach redirect URIs

* No firewall blocking OIDC flows

**Certificate Management**:

* Valid SSL certificates for all application domains

* PING trust of application SSL certificates

### 11.3 Team Coordination

**Identity Team**:

* PING configuration and user provisioning

* Group/role setup

* Support during testing and deployment

**Backend Team**:

* API token validation changes

* Role mapping implementation

* Testing and deployment

**Frontend Team**:

* OIDC integration

* UI/UX updates (if login flow changes)

* Testing

**Infrastructure Team**:

* Helm chart updates

* Terraform changes

* Secret management

* AKS configuration

**QA Team**:

* Test plan creation

* Functional testing

* Security testing

* UAT coordination

**Product/Business**:

* User communication

* Go/No-Go decision

* Stakeholder approval

### 11.4 Timeline Assumptions

**Total Duration**: 9-10 weeks

**Assumptions**:

* PING environments are ready in Week 1

* No major blockers or scope changes

* Resources available full-time

* Testing proceeds without major bugs

**Risks**:

* PING setup delays

* Scope mismatch (Azure AD scopes don't map directly to PING)

* User provisioning issues (users not synced to PING)

* Integration issues discovered in testing

* Backend API changes more complex than expected

---

## 12. Success Metrics

**Authentication Metrics**:

* Login success rate: >99%

* Token refresh success rate: >99.5%

* Average login time: \<3 seconds

* Average token refresh time: \<500ms

**Performance Metrics**:

* API latency increase: \<10% compared to Azure AD

* Frontend load time: No degradation

* Concurrent users supported: No decrease

**User Experience**:

* User satisfaction score: Maintained or improved

* Support tickets related to login: \<5 in first month

* User training required: Minimal (login flow similar)

**Security**:

* Zero security incidents related to authentication

* All security test cases pass

* Third-party security audit pass (if conducted)

---

## 13. Open Questions and Decisions

### Questions for Identity Team:

1. **PING Scope Structure**: What is the preferred scope naming convention in PING for SaaS environments?

\- Azure AD uses: `api://{app-id}/Resource.Action`

\- PING best practice for saas-npd and saas-prd?

2. **User Provisioning**:

\- Entra: Existing enterprise users (no changes)

\- PING: How are SaaS customers provisioned? Self-service registration?

3. **Role Mapping**:

\- Entra: Current group/role mapping continues

\- PING: Can PING provide group/role claims in JWT for SaaS users? What claim name?

4. **Multi-tenant**:

\- Does PING support multi-tenant scenarios for SaaS customers?

\- How do we isolate client data between SaaS customers?

5. **Session Management**:

\- Should Entra and PING have different session timeouts?

\- PING default timeout for SaaS customers?

6. **MFA**:

\- Entra: Current MFA policies unchanged

\- PING: Is MFA enabled for SaaS customers? Configuration?

7. **User Experience**:

\- Should login UI differ between Entra and PING environments?

\- Branding requirements for SaaS vs Enterprise?

### Questions for Backend Team:

7. **Token Validation**: Is the backend API currently validating Azure AD tokens? What library/framework is used?

8. **Authorization**: Are roles from Azure AD used for authorization? How are they checked?

9. **API Versioning**: Can we deploy backend changes independently of frontend?

### Questions for Infrastructure Team:

10. **Secret Management**: Which secret management solution should we use for PING configuration?

11. **Gateway Auth**: Should we implement PING token validation at the Istio gateway or only in application?

12. **Monitoring**: What metrics/logs should we add for PING authentication monitoring?

### Questions for Product/Business:

13. **User Communication**: Do we need to notify users before migration? What is the message?

14. **Downtime Window**: Is there a preferred deployment window for production?

15. **Training**: Do users need training on new login process, or is it transparent?

---

## 14. References and Resources

**PING Identity Documentation**:

* [PingOne for Customers - OIDC](https://docs.pingidentity.com/r/en-us/pingone/p1_t_addoidcapp)

* [PingFederate - OAuth/OIDC](https://docs.pingidentity.com/bundle/pingfederate-100/page/concept/oauthEndpoints.html)

* [PKCE Support](https://docs.pingidentity.com/r/en-us/pingone/p1_c_pkce)

**OIDC Client Libraries**:

* [oidc-client-ts](https://github.com/authts/oidc-client-ts)

* [react-oidc-context](https://github.com/authts/react-oidc-context)

**Terraform Providers**:

* [PingOne Terraform Provider](https://registry.terraform.io/providers/pingidentity/pingone/latest/docs)

**Azure AKS**:

* [Istio Authorization](https://istio.io/latest/docs/tasks/security/authorization/)

* [Workload Identity](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview)

**Testing**:

* [Playwright](https://playwright.dev/)

* [Jest](https://jestjs.io/)

---

## 15. Appendix

### A. Environment Configuration Template

```yaml

# Template for helm/values-{env}.yaml

  

app:

  environment: "{env}"  # dev, qa, uat, prd, saas-npd, saas-prd

  

env:

  NODE_ENV: production

  # PING Identity Configuration

  PING_AUTHORITY: "https://auth.pingone.<region>.com/{environment-id}/as"

  PING_CLIENT_ID: "{client-id}"

  PING_ISSUER: "https://auth.pingone.<region>.com/{environment-id}/as"

  PING_JWKS_URI: "https://auth.pingone.<region>.com/{environment-id}/as/jwks"

  PING_SCOPE: "openid profile email documents:read documents:write clients:read clients:write"

  # Backend API

  REACT_APP_API_URL: "https://api.{env}.claitdev.com/documentsai"

  REACT_APP_VERSION: 1.0.0

  

istio:

  hosts: [documentsai.{env}.claitdev.com]

  

serviceAccount:

  annotations:

    azure.workload.identity/client-id: "{azure-workload-identity-client-id}"  # If still needed

```

```

### B. Code Migration Checklist

  

**Frontend Files to Modify**:

- [ ] `/package.json` - Update dependencies

- [ ] `/src/components/AuthProvider.tsx` - Complete rewrite

- [ ] `/src/utilities/api.ts` - Update token acquisition

- [ ] `/src/routes/Login.tsx` - Update login UI

- [ ] `/src/components/Navigation.tsx` - Update logout

- [ ] `/src/index.jsx` - Wrap with new AuthProvider

- [ ] `/public/env.js` - Update environment variables

- [ ] `/50-load-env.sh` - Update environment variable loading

  

**New Frontend Files to Create**:

- [ ] `/src/auth/OidcConfig.ts` - OIDC configuration

- [ ] `/src/auth/OidcCallback.tsx` - Callback handler

- [ ] `/src/auth/SilentRenew.tsx` - Silent renewal (optional)

  

**Backend Files to Modify** (if applicable):

- [ ] Authentication middleware

- [ ] Token validation logic

- [ ] Authorization/role checking

- [ ] Configuration files

  

**Infrastructure Files to Modify**:

- [ ] `/helm/values-dev.yaml`

- [ ] `/helm/values-qa.yaml`

- [ ] `/helm/values-uat.yaml`

- [ ] `/helm/values-prd.yaml`

- [ ] `/helm/values-saas-npd.yaml`

- [ ] `/helm/values-saas-prd.yaml`

- [ ] `/.github/workflows/build-deploy.yml`

- [ ] `/.github/workflows/node-pr-validation.yml`

- [ ] `/docker-compose.yml` (for local development)

  

**Terraform Files** (if managing PING via Terraform):

- [ ] Create `/terraform/modules/pingone-application/`

- [ ] Update environment-specific Terraform

  

### C. Testing Checklist

  

**Unit Tests**:

- [ ] AuthProvider initialization

- [ ] Login flow

- [ ] Logout flow

- [ ] Token refresh

- [ ] API interceptor

- [ ] Role-based access

  

**Integration Tests**:

- [ ] End-to-end login

- [ ] API calls with token

- [ ] Token expiration and renewal

- [ ] Logout and token cleanup

  

**Security Tests**:

- [ ] JWT signature validation

- [ ] Expired token rejection

- [ ] Invalid issuer rejection

- [ ] PKCE flow verification

- [ ] XSS/CSRF protections

  

**Performance Tests**:

- [ ] Login time < 3s

- [ ] Token refresh < 500ms

- [ ] API latency baseline

- [ ] Load test: 100 concurrent users

  

**Browser Compatibility**:

- [ ] Chrome

- [ ] Firefox

- [ ] Safari

- [ ] Edge

  

**UAT Scenarios**:

- [ ] User can login with PING credentials

- [ ] User can access all features based on role

- [ ] User can logout successfully

- [ ] Session timeout works correctly

- [ ] Token refresh is seamless (no disruption)

  

---

  

## Document Revision History

  

| Version | Date | Author | Changes |

|---------|------|--------|---------|

| 1.0 | 2026-03-05 | AI Assistant | Initial high-level design |

  

---

  

## Approval

  

**Prepared by**: Development Team

  

**Reviewed by**:

- [ ] Identity Team Lead

- [ ] Backend Lead

- [ ] Infrastructure Lead

- [ ] Security Team

- [ ] Product Owner

  

**Approved by**:

- [ ] Engineering Director

- [ ] CISO (if required)

  

**Approval Date**: _________________

  

---

  

**Next Steps**:

1. Review this document with all stakeholders

2. Address open questions (Section 13)

3. Obtain approvals

4. Begin Phase 0 (Prerequisites)
```