From ffb0bc1019ce2aab16ef18a0b4212ae9486db217 Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Mon, 6 Jul 2026 20:24:14 +0200 Subject: [PATCH] feat(web): local dev against deployed API without prod config changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - msalConfig: NEXT_PUBLIC_ENTRA_API_CLIENT_ID splits the API scope from the SPA client id, so local dev signs in with the dev app registration while requesting the prod API audience. Unset in prod (fallback keeps the single-app behavior). - next.config: dev-only rewrites() proxy forwards /api/* server-side to DEV_API_PROXY_TARGET, keeping the browser same-origin — no localhost CORS entry on the prod Function App. - CSP: script-src gains unsafe-eval only when NODE_ENV=development (next dev HMR/react-refresh needs eval); production CSP unchanged. Co-Authored-By: Claude Fable 5 --- src/Web/autopilot-monitor-web/lib/msalConfig.ts | 14 +++++++++----- src/Web/autopilot-monitor-web/next.config.ts | 15 ++++++++++++++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/Web/autopilot-monitor-web/lib/msalConfig.ts b/src/Web/autopilot-monitor-web/lib/msalConfig.ts index 99dc3679..f8757009 100644 --- a/src/Web/autopilot-monitor-web/lib/msalConfig.ts +++ b/src/Web/autopilot-monitor-web/lib/msalConfig.ts @@ -79,13 +79,17 @@ export const loginRequest: RedirectRequest = { * Scopes for accessing the backend API * IMPORTANT: Backend API must be exposed in Azure AD App Registration with this scope * Format: api:///access_as_user - * For testing, you can also use User.Read but disable signature validation in backend + * + * NEXT_PUBLIC_ENTRA_API_CLIENT_ID lets local dev sign in with the dev app + * registration while requesting a token whose audience is the prod API app + * (the deployed backend only accepts its own audience). Unset in production, + * where one app registration serves as both SPA client and API. */ +const apiAppId = + process.env.NEXT_PUBLIC_ENTRA_API_CLIENT_ID || process.env.NEXT_PUBLIC_ENTRA_CLIENT_ID; + export const apiRequest = { - scopes: [ - // Option 1 (recommended): Use backend API scope after exposing API in Azure AD - `api://${process.env.NEXT_PUBLIC_ENTRA_CLIENT_ID}/access_as_user` - ], + scopes: [`api://${apiAppId}/access_as_user`], }; /** diff --git a/src/Web/autopilot-monitor-web/next.config.ts b/src/Web/autopilot-monitor-web/next.config.ts index cd6aed61..50a4c87c 100644 --- a/src/Web/autopilot-monitor-web/next.config.ts +++ b/src/Web/autopilot-monitor-web/next.config.ts @@ -51,6 +51,17 @@ const nextConfig: NextConfig = { { source: "/roadmap", destination: "/", permanent: false }, ]; }, + async rewrites() { + // Dev-only reverse proxy: with DEV_API_PROXY_TARGET set in .env.local, + // /api/* is forwarded server-side through the Next dev server to the + // deployed backend. The browser stays same-origin, so the prod Function + // App needs no localhost CORS entry. Inactive in production builds. + const target = process.env.DEV_API_PROXY_TARGET; + if (process.env.NODE_ENV !== "development" || !target) { + return []; + } + return [{ source: "/api/:path*", destination: `${target}/api/:path*` }]; + }, async headers() { return [ { @@ -64,7 +75,9 @@ const nextConfig: NextConfig = { key: "Content-Security-Policy", value: [ "default-src 'self'", - "script-src 'self' 'unsafe-inline'", + // Dev-only: next dev serves HMR/react-refresh chunks through eval(); + // production stays strict without 'unsafe-eval'. + `script-src 'self' 'unsafe-inline'${process.env.NODE_ENV === "development" ? " 'unsafe-eval'" : ""}`, "style-src 'self' 'unsafe-inline'", "img-src 'self' data: blob: https://*.tile.openstreetmap.org", "font-src 'self'",