Skip to content

Move OIDC client secret from frontend to backend (oclweb3 + oclapi2) #2407

@paynejd

Description

@paynejd

Summary

The OIDC authorization code exchange flow currently requires the frontend (oclweb3) to hold and transmit the OIDC_RP_CLIENT_SECRET to the backend. Per OAuth 2.0 best practices, confidential client secrets should only be stored and used server-side.

Current behavior

  1. OIDC_RP_CLIENT_SECRET is injected into browser-accessible JavaScript in two ways:
    • At runtime via env-config.js (window.OIDC_RP_CLIENT_SECRET) — written by start-prod.sh
    • At build time via webpack DefinePlugin (process.env.OIDC_RP_CLIENT_SECRET) — in webpack.config.js
  2. The OIDLoginCallback component (src/components/users/OIDLoginCallback.jsx) reads the secret from the browser context and sends it in a POST request to the backend's oidc/code-exchange/ endpoint
  3. The backend (core/users/views.pyOIDCodeExchangeView) receives client_id and client_secret from the request body and uses them to exchange the authorization code for a token

Expected behavior

The backend should store OIDC_RP_CLIENT_ID and OIDC_RP_CLIENT_SECRET in its own environment and use them server-side during the code exchange. The frontend should only send code and redirect_uri.

Step 1: Ecosystem-wide audit (REQUIRED FIRST)

OCL Online is a complex ecosystem with many independent services and applications. Before making any code changes, a thorough audit must be conducted across the entire OCL Online ecosystem to identify all clients, applications, and services that handle OIDC client secrets in a similar way. This includes but is not limited to:

  • oclweb3 and oclweb2 (frontends)
  • oclapi2 (backend)
  • Any other services that participate in OIDC flows (e.g. FHIR services, analytics, admin tools)
  • Docker compose configurations and deployment scripts across all environments
  • KeyCloak client registrations and their configurations

The goal is to ensure that all affected components are revised together, not just the ones identified so far.

Proposed changes (pending audit results)

Backend (oclapi2):

  • core/settings.py: Populate OIDC_RP_CLIENT_ID and OIDC_RP_CLIENT_SECRET from environment variables (placeholder settings already exist at lines 564-582)
  • core/users/views.py: Update OIDCodeExchangeView.post() to read client_id / client_secret from settings instead of the request body
  • core/integration_tests/tests_users.py: Update OIDCodeExchangeViewTest to match new endpoint contract

Frontend (oclweb3):

  • src/components/users/OIDLoginCallback.jsx: Only send code and redirect_uri in the POST to oidc/code-exchange/
  • webpack.config.js: Remove OIDC_RP_CLIENT_SECRET from DefinePlugin
  • start-prod.sh: Remove OIDC_RP_CLIENT_SECRET from env-config.js generation
  • src/common/utils.js: Update isSSOEnabled() to check only LOGIN_REDIRECT_URL + OIDC_RP_CLIENT_ID (since the secret would no longer be in the frontend)

Other services: To be determined by the audit in Step 1.

Deployment note

Requires coordinated deployment: backend first (accept both old and new request formats during transition), then frontends/services, then remove backward compatibility from backend.

Labels

Security, oclweb3, oclapi2

Metadata

Metadata

Labels

reviewed/keepReviewed during backlog cleanup - confirmed to keep open

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions