feat(web): local dev against deployed API without prod config changes

- 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 <noreply@anthropic.com>
This commit is contained in:
Oliver Kieselbach
2026-07-06 20:24:14 +02:00
parent 0f12ca6531
commit ffb0bc1019
2 changed files with 23 additions and 6 deletions
@@ -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://<backend-client-id>/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`],
};
/**
+14 -1
View File
@@ -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'",