Skip to content

Feature/347 Replace passport-azure-ad with openid-client - #662

Merged
junaidiqbalmoj merged 11 commits into
masterfrom
feature/347-replace-passport-azure-ad
Jun 5, 2026
Merged

junaidiqbalmoj merged 11 commits into
masterfrom
feature/347-replace-passport-azure-ad

Conversation

@KianKwa

@KianKwa KianKwa commented May 21, 2026

Copy link
Copy Markdown
Contributor

Jira link

#347

Change description

Replace passport-azure-ad with openid-client

Checklist

  • commit messages are meaningful and follow good commit message guidelines
  • README and other documentation has been updated / added (if needed)
  • tests have been updated / new tests has been added (if needed)
  • Does this PR introduce a breaking change

Summary by CodeRabbit

  • Chores

    • SSO configuration switched to an issuer URL (environment variable renamed to SSO_ISSUER_URL) and authentication backend migrated to a modern OIDC client/strategy.
  • Documentation

    • README, examples and deployment values updated to reflect the new SSO environment variable and setup.
  • Bug Fixes

    • Reduced noisy diagnostic logs during authentication callbacks.
    • Default IDAM scope fallback now includes email (openid profile email).

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replace passport-azure-ad with openid-client, make configurePassport async to run OIDC discovery, rename SSO config to issuerUrl/SSO_ISSUER_URL, register "sso-oidc" strategy, update routes/tests/docs/CI/helm, and adjust a few ancillary configs and logs.

Changes

SSO Library Migration & OIDC Strategy Implementation

Layer / File(s) Summary
Environment & Deployment Configuration Updates
.github/workflows/e2e.yml, .github/workflows/nightly.yml, README.md, apps/web/.env.example, apps/web/config/custom-environment-variables.json, apps/web/helm/values.dev.yaml, apps/web/helm/values.yaml
CI/Helm/README/.env mappings switched from SSO_IDENTITY_METADATASSO_ISSUER_URL for SSO secret exposure.
Ticket Documentation & Tasks
docs/tickets/347/plan.md, docs/tickets/347/tasks.md, docs/tickets/347/ticket.md
Add migration plan, tasks, and ticket documentation describing openid-client approach, async discovery, strategy name, and required changes.
Dependency Management: Passport Library Swap
libs/auth/package.json, package.json
Removed passport-azure-ad and its types; added openid-client@6.8.4; added package resolutions.
SSO Config Schema & Environment Variable Updates
libs/auth/src/config/sso-config.ts, libs/auth/src/config/sso-config.test.ts, apps/web/config/custom-environment-variables.json
SsoConfig now uses issuerUrl from SSO_ISSUER_URL; removed appendOpenIdConfigPath; getSsoConfig()/isSsoConfigured() updated and tests adjusted.
Passport OIDC Strategy & Async Configuration
libs/auth/src/config/passport-config.ts, libs/auth/src/config/passport-config.test.ts
configurePassport is async and runs Issuer.discover(issuerUrl); strategy registered as "sso-oidc"; verify callback uses tokens.claims() with Graph profile fallback; tests refactored to mock openid-client.
Strategy Name Updates in Login & SSO Callback Routes
libs/auth/src/pages/login/index.ts, libs/auth/src/pages/login/index.test.ts, libs/auth/src/pages/sso-callback/index.ts, libs/auth/src/pages/sso-callback/index.test.ts
Routes and tests updated to call passport.authenticate(...) with "sso-oidc" instead of "azuread-openidconnect"; new guard tests added for unconfigured SSO.
Logout Tenant ID Extraction from Issuer URL
libs/auth/src/pages/logout/index.ts, libs/auth/src/pages/logout/index.test.ts
extractTenantId now parses tenant ID from issuerUrl (allows /v2.0 end or trailing slash); logout handler and tests updated.
Await Async Passport Configuration in App Startup
apps/web/src/app.ts
configurePassport(app) call changed to await configurePassport(app) to ensure discovery completes before other middleware.
Crime IDAM Default Scope Change
libs/auth/src/config/crime-idam-config.ts, libs/auth/src/config/crime-idam-config.test.ts
Default CRIME_IDAM_SCOPE fallback changed to "openid profile email"; tests updated.
CFT Callback Diagnostic Cleanup
libs/auth/src/pages/cft-callback/index.ts
Removed diagnostic console.log statements; session/login flow unchanged.
Blob Ingestion Validation Comment
libs/api/src/blob-ingestion/validation.ts
Removed an inline comment before the size check.

Sequence Diagram

sequenceDiagram
  participant App as apps/web
  participant ConfigurePassport as configurePassport
  participant Issuer as Issuer.discover
  participant Passport as passport
  App->>ConfigurePassport: await configurePassport(app)
  ConfigurePassport->>Issuer: Issuer.discover(issuerUrl)
  Issuer-->>ConfigurePassport: OIDC metadata
  ConfigurePassport->>Passport: passport.use("sso-oidc", new Strategy(...))
Loading

Possibly related issues

Possibly related PRs

  • hmcts/cath-service#121: Both PRs touch SSO auth flow wiring in libs/auth: the retrieved PR adds redirectUnauthenticated/requireRole logic that branches on isSsoConfigured(), while the main PR changes isSsoConfigured to validate issuerUrl (via SSO_ISSUER_URL) instead of identityMetadata (via SSO_IDENTITY_METADATA), directly affecting that redirect behavior.
  • hmcts/cath-service#116: Both PRs modify the SSO callback handler (libs/auth/src/pages/sso-callback/index.ts)—the main PR changes the Passport strategy to "sso-oidc", while the retrieved PR adds/updates user upsert logic in the same flow—so the changes are related.
  • hmcts/cath-service#338: Both PRs modify apps/web/src/app.ts in the authentication setup area involving configurePassport/passport middleware ordering (main PR makes configurePassport async/awaited; retrieved PR reworks the auth import/route middleware wiring around that call).
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: replacing passport-azure-ad with openid-client as the primary authentication library.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/347-replace-passport-azure-ad

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (6)
docs/tickets/347/plan.md (2)

27-27: ⚡ Quick win

Verify the client class name.

The openid-client v5 API uses new issuer.Client({...}) rather than new issuer.BaseClient({...}). BaseClient is an internal base class not typically instantiated directly.

📝 Suggested correction
-- `new issuer.BaseClient({ client_id, client_secret, redirect_uris, response_types })` — creates the client
+- `new issuer.Client({ client_id, client_secret, redirect_uris, response_types })` — creates the client

78-100: 💤 Low value

Add language specifiers to fenced code blocks.

The code blocks at lines 78 and 88 are missing language identifiers, which would improve syntax highlighting and markdown quality.

📝 Suggested fix
-```
+```typescript
 import { Issuer } from "openid-client";
libs/auth/src/config/passport-config.ts (3)

10-34: 💤 Low value

Module ordering: exported function should precede helpers.

The coding guidelines require exported functions to appear before other functions in usage order. verifyOidcCallback (a non-exported helper) is currently declared above the exported configurePassport. Consider moving verifyOidcCallback below configurePassport.

As per coding guidelines: "Module ordering: constants outside function scope at top, exported functions next, other functions in usage order, interfaces and types at bottom."


37-67: ⚡ Quick win

Duplicated passport bootstrap in two early-return branches.

The disableSso branch and the missing-config branch register the same initialize/session/serializeUser/deserializeUser setup verbatim. Extracting a small helper (e.g. registerNoOpPassport(app)) would remove the duplication and keep both fallback paths in lockstep if future tweaks are needed.

♻️ Suggested extraction
+function registerNoOpPassport(app: Express): void {
+  app.use(passport.initialize());
+  app.use(passport.session());
+  passport.serializeUser((user, done) => done(null, user));
+  passport.deserializeUser((user: Express.User, done) => done(null, user));
+}
+
 export async function configurePassport(app: Express): Promise<void> {
   const disableSso = process.env.NODE_ENV === "development" && !process.env.ENABLE_SSO;
-
-  if (disableSso) {
-    app.use(passport.initialize());
-    app.use(passport.session());
-    passport.serializeUser((user, done) => { done(null, user); });
-    passport.deserializeUser((user: Express.User, done) => { done(null, user); });
-    return;
-  }
+  if (disableSso) {
+    registerNoOpPassport(app);
+    return;
+  }

   const ssoConfig = getSsoConfig();

   if (!ssoConfig.issuerUrl || !ssoConfig.clientId || !ssoConfig.clientSecret) {
-    app.use(passport.initialize());
-    app.use(passport.session());
-    passport.serializeUser((user, done) => { done(null, user); });
-    passport.deserializeUser((user: Express.User, done) => { done(null, user); });
+    registerNoOpPassport(app);
     return;
   }

69-84: ⚡ Quick win

Confirm openid-client/passport v6.8.4 Strategy + discovery usage (and improve malformed issuer URL error clarity)

  • Strategy options: callbackURL and scope are supported openid-client/passport StrategyOptions fields, and the repo’s unit test already asserts those exact option names/values.
  • discovery(server, clientId, clientSecret): the 3rd positional argument as a string is supported as a shorthand for client_secret (so your positional form is fine).
  • Consider wrapping new URL(ssoConfig.issuerUrl) in a try/catch (libs/auth/src/config/passport-config.ts:69) to surface a clear “invalid SSO_ISSUER_URL” configuration error instead of a raw TypeError.
libs/auth/src/pages/logout/index.ts (1)

5-8: 💤 Low value

Tenant regex is restricted to lowercase hex GUIDs.

/\/([a-f0-9-]+)\/v2\.0(?:\/|$)/ will fail to extract the tenant when the issuer URL uses uppercase hex (Azure occasionally emits these) or a domain-style tenant like contoso.onmicrosoft.com. The v2.0 issuer from discovery is typically the lowercase GUID, so this is unlikely to bite in practice, but adding the i flag and broadening the character class would make this resilient to either form.

♻️ Suggested regex
-  const match = issuerUrl.match(/\/([a-f0-9-]+)\/v2\.0(?:\/|$)/);
+  const match = issuerUrl.match(/\/([^/]+)\/v2\.0(?:\/|$)/i);

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 13877257-5159-4315-9b39-ac1617d83257

📥 Commits

Reviewing files that changed from the base of the PR and between 447a418 and e7ef007.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (22)
  • .github/workflows/e2e.yml
  • .github/workflows/nightly.yml
  • README.md
  • apps/web/.env.example
  • apps/web/config/custom-environment-variables.json
  • apps/web/helm/values.dev.yaml
  • apps/web/helm/values.yaml
  • apps/web/src/app.ts
  • docs/tickets/347/plan.md
  • docs/tickets/347/tasks.md
  • docs/tickets/347/ticket.md
  • libs/auth/package.json
  • libs/auth/src/config/passport-config.test.ts
  • libs/auth/src/config/passport-config.ts
  • libs/auth/src/config/sso-config.test.ts
  • libs/auth/src/config/sso-config.ts
  • libs/auth/src/pages/login/index.test.ts
  • libs/auth/src/pages/login/index.ts
  • libs/auth/src/pages/logout/index.test.ts
  • libs/auth/src/pages/logout/index.ts
  • libs/auth/src/pages/sso-callback/index.ts
  • package.json

@github-actions

github-actions Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

🎭 Playwright E2E Test Results

84 tests   51 ✅  6m 8s ⏱️
33 suites  33 💤
 1 files     0 ❌

Results for commit 1445e93.

♻️ This comment has been updated with latest results.

@github-actions

Copy link
Copy Markdown
Contributor

Preview Deployment Successful 🚀

Your preview environment is ready:

The environment will be automatically cleaned up when this PR is closed.

@KianKwa KianKwa linked an issue May 22, 2026 that may be closed by this pull request

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 81a45d9d-eea9-4468-9225-93034b51ea3a

📥 Commits

Reviewing files that changed from the base of the PR and between e7ef007 and 8bfb281.

📒 Files selected for processing (5)
  • libs/auth/src/config/crime-idam-config.test.ts
  • libs/auth/src/config/crime-idam-config.ts
  • libs/auth/src/config/sso-config.test.ts
  • libs/auth/src/pages/cft-callback/index.ts
  • libs/auth/src/pages/sso-callback/index.test.ts
💤 Files with no reviewable changes (1)
  • libs/auth/src/pages/cft-callback/index.ts

vi.mocked(isSsoConfigured).mockReturnValue(true);

const mockMiddleware = vi.fn();
vi.mocked(passport.authenticate).mockReturnValue(mockMiddleware as any);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify remaining `as any` usages in this test file.
rg -nP '\bas any\b' libs/auth/src/pages/sso-callback/index.test.ts

Repository: hmcts/cath-service

Length of output: 581


🏁 Script executed:

#!/bin/bash
# Inspect the relevant parts around the flagged `as any` usages.
sed -n '35,90p' libs/auth/src/pages/sso-callback/index.test.ts
echo "----"
sed -n '120,260p' libs/auth/src/pages/sso-callback/index.test.ts
echo "----"
sed -n '330,390p' libs/auth/src/pages/sso-callback/index.test.ts

Repository: hmcts/cath-service

Length of output: 7060


Remove the as any cast in the passport.authenticate middleware mock

Line 55 weakens type-safety; type mockMiddleware directly so the as any cast is unnecessary.

Suggested change
-    const mockMiddleware = vi.fn();
-    vi.mocked(passport.authenticate).mockReturnValue(mockMiddleware as any);
+    const mockMiddleware: (req: Request, res: Response, next: () => void) => void = vi.fn();
+    vi.mocked(passport.authenticate).mockReturnValue(mockMiddleware);

Also, the file contains several other as any / cb: any usages in session-save and user update mocks; they should be cleaned up using typed mocks to match strict-mode expectations.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
vi.mocked(passport.authenticate).mockReturnValue(mockMiddleware as any);
const mockMiddleware: (req: Request, res: Response, next: () => void) => void = vi.fn();
vi.mocked(passport.authenticate).mockReturnValue(mockMiddleware);

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment Successful 🚀

Your preview environment is ready:

The environment will be automatically cleaned up when this PR is closed.

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment Successful 🚀

Your preview environment is ready:

The environment will be automatically cleaned up when this PR is closed.

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment Successful 🚀

Your preview environment is ready:

The environment will be automatically cleaned up when this PR is closed.

@sonarqubecloud

sonarqubecloud Bot commented Jun 5, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment Successful 🚀

Your preview environment is ready:

The environment will be automatically cleaned up when this PR is closed.

@junaidiqbalmoj
junaidiqbalmoj merged commit 832fbcc into master Jun 5, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace passport-azure-ad with openid-client

3 participants