AUTH4D-20 developer-console shell

The console single-page app at {CONSOLE_ORIGIN} talks only to its own backend-for-frontend (AUTH4D-17). It never receives, stores or forwards management tokens.

Browser-to-BFF contract

All calls go through apps/console/src/core/api-client.ts:

  • Paths must be relative /api/... paths with plain segments. Absolute, protocol-relative, dot, percent-encoded or empty segments are rejected before any request is sent.
  • Requests use mode: "same-origin", credentials: "same-origin", cache: "no-store" and redirect: "manual". State-changing methods send a JSON body with Content-Type: application/json; the browser attaches the Origin header the BFF checks for CSRF. The session cookie is HttpOnly and SameSite=Strict. No Authorization header is ever set.
  • Redirects and non-JSON success bodies are treated as invalid responses. 204 resolves empty.
  • Error bodies are accepted as {"error":"code"} (BFF) or {"error":{"code","request_id"}} (management). X-Request-Id is used when the envelope has no request ID. The UI shows request IDs in error messages.
  • Any 401 (except the current-session probe) marks the session expired. The console then shows a modal “Your session has expired” dialog whose action re-runs sign-in.
Console action Request Handling
Session probe GET /api/auth/current 200 signed in, 401 sign-in screen, other errors “unavailable”
Sign in top-level form POST /api/auth/start BFF redirects to the hosted login; callback returns to /
Callback errors /?auth_error=invalid_state|sign_in_failed shown on the sign-in screen and removed from the URL
Sign out POST /api/auth/sign-out, body {} 204 returns to sign-in; failures keep the session and show an error
Project list GET /api/management/tenants rows in snake_case (tenant_id, display_name, status, role) or contract camelCase

Unknown /api/* paths now return 404 {"error":"not_found"} from the BFF instead of the single-page-application fallback. Static assets use not_found_handling: "single-page-application", so deep links such as /projects/{tenantId}/users load the app.

Routes and navigation (frozen)

Path Screen Navigation group
/ redirects to /projects
/projects project selection (core)
/projects/new features/project-create
/projects/{tenantId} features/project-overview Project
/projects/{tenantId}/applications features/applications Project
/projects/{tenantId}/sign-in-methods features/sign-in-methods Project
/projects/{tenantId}/users features/users Project
/projects/{tenantId}/settings features/project-settings Project
/projects/{tenantId}/sessions features/sessions Operations
/projects/{tenantId}/audit-log features/audit-log Operations

{tenantId} must match ten_[A-Za-z0-9]{16,64}. The route table lives in apps/console/src/routes.ts. Feature tickets fill only their features/<id>/index.tsx default export: sections receive FeatureScreenProps (project, api), and /projects/new receives CreateProjectScreenProps (api, reloadProjects). They import only from src/core/index.ts. The shell renders the page <h1> (the section label) and the project-name eyebrow. Features start their headings at <h2>.

Project selection is presentation state carried in the URL. A project that is not in the signed-in developer’s list shows “Project unavailable” and does not mount the feature. The API still authorizes every request from the authenticated actor and the requested tenant. Nothing is written to localStorage, sessionStorage, IndexedDB or script-readable cookies.

Tests

  • tests/integration/auth4d-20.console-core.test.ts (Vitest): API-client path safety, request options, 401 expiry, error envelopes, redirects and 204 handling; route resolution; project normalization.
  • tests/e2e/auth4d-20.console-shell.spec.ts (Playwright, Chromium, deterministic mock BFF): sign-in, callback errors, loading, unavailable, empty and error states, keyboard navigation (skip link, focus on route change, project switcher, Escape), inaccessible projects, the expired- session modal, sign-out success and failure, and a 360×740 mobile viewport without horizontal overflow. Every test also asserts that nothing was written to browser storage. Run it with pnpm exec playwright test -c apps/console/playwright.config.ts. The config builds to apps/console/.wrangler/ui-test-dist and serves it on port 5185.

Source: docs/api-spec/auth4d-20.md