From 82d8c6972fb8559361fc128650d00a0474494dac Mon Sep 17 00:00:00 2001 From: Julien Gautier Date: Mon, 1 Jun 2026 16:13:31 +0200 Subject: [PATCH] feat(spa): opt-in 'https' nx serve config for dev-server TLS Entra refuses http: redirect URIs for any host other than localhost, which blocked the ADR-0030 dockerised dev mode the moment a developer tried to share the stack with another teammate via a hostname (e.g. apf-portal.dev.local). Add an opt-in 'https' Nx serve configuration on both SPAs so the dev-server can terminate TLS using a mkcert cert, and let the apps Compose profile pick the configuration via an env var so behaviour stays unchanged by default. - apps/portal-{shell,admin}/project.json: new https configuration inheriting development + ssl/sslKey/sslCert at .secrets/dev-tls.*. - infra/local/dev.compose.yml: shell + admin commands gain --configuration=${NX_SERVE_CONFIGURATION:-development}, interpolated from infra/local/.env at parse time. - infra/local/.env.example: NX_SERVE_CONFIGURATION block + rationale. - apps/portal-bff/.env.example: comment block above the ENTRA_*_ REDIRECT_URI defaults shows the https hostname override pattern and reminds that each URI must be registered Entra-side. - infra/README.md: new HTTPS dev-server setup subsection covering mkcert install, cert generation, the .secrets/ convention, the Entra step, and how to flip NX_SERVE_CONFIGURATION on. Native WSL / localhost is unaffected: defaultConfiguration stays development, no SSL files required, existing Entra localhost URIs work as before. BFF stays plain HTTP behind the SPA proxy. --- apps/portal-admin/project.json | 6 +++++ apps/portal-bff/.env.example | 19 +++++++++++++++ apps/portal-shell/project.json | 6 +++++ infra/README.md | 44 ++++++++++++++++++++++++++++++++++ infra/local/.env.example | 17 +++++++++++++ infra/local/dev.compose.yml | 37 ++++++++++++++++++++++++++-- 6 files changed, 127 insertions(+), 2 deletions(-) diff --git a/apps/portal-admin/project.json b/apps/portal-admin/project.json index 4fd72a7..c49fa6a 100644 --- a/apps/portal-admin/project.json +++ b/apps/portal-admin/project.json @@ -93,6 +93,12 @@ }, "development": { "buildTarget": "portal-admin:build:development" + }, + "https": { + "buildTarget": "portal-admin:build:development", + "ssl": true, + "sslKey": ".secrets/dev-tls.key", + "sslCert": ".secrets/dev-tls.pem" } }, "defaultConfiguration": "development" diff --git a/apps/portal-bff/.env.example b/apps/portal-bff/.env.example index 15eef63..c5291e8 100644 --- a/apps/portal-bff/.env.example +++ b/apps/portal-bff/.env.example @@ -60,6 +60,25 @@ ENTRA_CLIENT_SECRET=replace_with_real_value # User portal — `/api/auth/callback` is the OIDC return URL; the # post-logout URL is where Entra sends the browser after RP-initiated # logout (typically the SPA landing page). +# +# The default values below match WSL-native dev (browser on the same +# host as the BFF, accessing http://localhost:4200/). For the +# ADR-0030 dockerised dev mode accessed via a hostname (e.g. +# https://apf-portal.dev-jg.local:4200/), override these to point at +# the SPA dev-server origin instead — `/api/auth/callback` is then +# proxied to the BFF (see apps/portal-shell/proxy.conf.js): +# +# ENTRA_REDIRECT_URI=https://apf-portal.dev-jg.local:4200/api/auth/callback +# ENTRA_POST_LOGOUT_REDIRECT_URI=https://apf-portal.dev-jg.local:4200/ +# ENTRA_ADMIN_REDIRECT_URI=https://apf-portal.dev-jg.local:4300/api/admin/auth/callback +# ENTRA_ADMIN_POST_LOGOUT_REDIRECT_URI=https://apf-portal.dev-jg.local:4300/ +# +# The `https:` scheme is mandatory for non-`localhost` hosts — Entra +# rejects `http:` for anything else. See the "HTTPS dev-server setup" +# section of infra/README.md for the dev-server TLS / mkcert setup. +# Each override URI must also be registered in the Entra app +# registration's Redirect URIs list (the BFF sends *one* of them per +# auth request; Entra validates it is allowed). ENTRA_REDIRECT_URI=http://localhost:3000/api/auth/callback ENTRA_POST_LOGOUT_REDIRECT_URI=http://localhost:4200/ # Admin portal — distinct callback per ADR-0020 §"Sessions — distinct diff --git a/apps/portal-shell/project.json b/apps/portal-shell/project.json index a719e41..245f2c1 100644 --- a/apps/portal-shell/project.json +++ b/apps/portal-shell/project.json @@ -92,6 +92,12 @@ }, "development": { "buildTarget": "portal-shell:build:development" + }, + "https": { + "buildTarget": "portal-shell:build:development", + "ssl": true, + "sslKey": ".secrets/dev-tls.key", + "sslCert": ".secrets/dev-tls.pem" } }, "defaultConfiguration": "development" diff --git a/infra/README.md b/infra/README.md index 87340bd..1f9285d 100644 --- a/infra/README.md +++ b/infra/README.md @@ -226,6 +226,50 @@ How it works (see [ADR-0030](../docs/decisions/0030-dockerised-dev-mode.md)): The three dev modes (native `nx serve`, devcontainer, this `apps` profile) and when to use each are summarised in [docs/setup/01-dev-debian-vm-setup.md](../docs/setup/01-dev-debian-vm-setup.md). +### HTTPS dev-server setup — remote-browser access via a hostname + +By default the dev-servers serve plain HTTP — fine when the browser is on the same host as the BFF (`http://localhost:4200/`), which is also the only HTTP origin Entra accepts as a redirect URI. The moment you access the SPA over a **hostname** (e.g. `apf-portal.dev.local`, useful when the browser sits on a workstation and the stack runs on a shared / per-dev VM), Entra refuses the `http:` redirect URI and the dev-servers must terminate TLS. + +The setup is one-time per dev: + +1. **Install [mkcert](https://github.com/FiloSottile/mkcert)** on your workstation (the machine where the browser runs) and bootstrap its local CA: + + ```bash + # Debian / WSL Ubuntu: + sudo apt install -y libnss3-tools + # macOS: + # brew install mkcert nss + # Windows (PowerShell, choco): + # choco install mkcert + mkcert -install + ``` + +2. **Generate the cert** for the hostname you registered in your `/etc/hosts` and in Entra. From the repo root on your workstation: + + ```bash + mkdir -p .secrets + mkcert -key-file .secrets/dev-tls.key -cert-file .secrets/dev-tls.pem \ + apf-portal.dev-jg.local # ← replace with YOUR hostname + ``` + + `.secrets/` is git-ignored; the bind-mount in the `apps` profile (the repo at `/workspace`) makes the files visible inside the containers at the path the `https` configuration expects. + +3. **Update `apps/portal-bff/.env`** so the BFF tells Entra the matching HTTPS URIs — see the redirect-URI block in [`apps/portal-bff/.env.example`](../apps/portal-bff/.env.example) for the override pattern. The same URIs must be registered in your Entra app registration's "Redirect URIs" list (the BFF only sends one of them per auth request; Entra validates it is on the list). + +4. **Enable the `https` Nx serve configuration** for the compose dev-servers by adding to `infra/local/.env`: + + ```env + NX_SERVE_CONFIGURATION=https + ``` + + The compose command resolves `--configuration=${NX_SERVE_CONFIGURATION:-development}` at parse time, so the SPAs pick up the `https` config defined in `apps/portal-shell/project.json` and `apps/portal-admin/project.json`. The BFF stays HTTP behind the proxy — only the public origin is HTTPS. + +5. `./infra/local/dev.sh up apps` → browser opens `https://apf-portal.dev-jg.local:4200/`. No cert warning (mkcert's CA is trusted by the workstation after step 1). + +Native `nx serve` (WSL / localhost) is **unaffected** — it keeps using the `development` configuration by default, no SSL required, and the `localhost` URIs registered in Entra still work. + +When real DNS + corp-CA-signed certs arrive, the hostname can be reused as-is (Entra registrations are literal strings — they don't care who signs the cert). Drop the cert files back into `.secrets/` and remove the mkcert step. + ### Service endpoints (defaults) | Service | Host port | Purpose | diff --git a/infra/local/.env.example b/infra/local/.env.example index c3965e6..605f668 100644 --- a/infra/local/.env.example +++ b/infra/local/.env.example @@ -35,3 +35,20 @@ OTEL_HTTP_PORT=4318 PGWEB_PORT=8081 JAEGER_UI_PORT=16686 SERVE_STATIC_PORT=4200 + +# ---------------------------------------------------------------- Apps (`--profile apps`) +# Nx serve configuration for the SPA dev-servers. Two values matter: +# - `development` (default) — plain HTTP. Use with `localhost` +# browser access; Entra accepts +# `http://localhost:*` redirect URIs. +# - `https` — TLS-terminating dev-server using the +# cert at `.secrets/dev-tls.{key,pem}`. +# Required when accessing the SPA via a +# hostname (e.g. `apf-portal.dev.local`) +# since Entra rejects `http:` for any +# non-`localhost` redirect URI. See the +# "HTTPS dev-server setup" section of +# infra/README.md for the mkcert +# procedure and how to wire the +# matching `ENTRA_REDIRECT_URI` value. +# NX_SERVE_CONFIGURATION=https diff --git a/infra/local/dev.compose.yml b/infra/local/dev.compose.yml index dcb4cde..aa25dc0 100644 --- a/infra/local/dev.compose.yml +++ b/infra/local/dev.compose.yml @@ -266,7 +266,27 @@ services: # /api proxy at the BFF container by name (Compose DNS). Native # `nx serve` leaves BFF_TARGET unset and falls back to localhost. BFF_TARGET: http://portal-bff:3000 - command: ['pnpm', 'exec', 'nx', 'serve', 'portal-shell', '--host', '0.0.0.0', '--port', '4200'] + # `--configuration=${NX_SERVE_CONFIGURATION:-development}` is + # interpolated by Compose at YAML parse time from + # `infra/local/.env`. Set `NX_SERVE_CONFIGURATION=https` there to + # serve over TLS — required when accessing via a hostname (Entra + # rejects `http:` for non-`localhost` redirect URIs). See the + # `https` configuration in apps/portal-shell/project.json + the + # "HTTPS dev-server setup" section in infra/README.md for the + # mkcert procedure. + command: + [ + 'pnpm', + 'exec', + 'nx', + 'serve', + 'portal-shell', + '--host', + '0.0.0.0', + '--port', + '4200', + '--configuration=${NX_SERVE_CONFIGURATION:-development}', + ] ports: - '${SHELL_PORT:-4200}:4200' depends_on: @@ -279,7 +299,20 @@ services: environment: # See portal-shell — same proxy target for the admin SPA. BFF_TARGET: http://portal-bff:3000 - command: ['pnpm', 'exec', 'nx', 'serve', 'portal-admin', '--host', '0.0.0.0', '--port', '4300'] + # See portal-shell for the NX_SERVE_CONFIGURATION rationale. + command: + [ + 'pnpm', + 'exec', + 'nx', + 'serve', + 'portal-admin', + '--host', + '0.0.0.0', + '--port', + '4300', + '--configuration=${NX_SERVE_CONFIGURATION:-development}', + ] ports: - '${ADMIN_PORT:-4300}:4300' depends_on: