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"andredirect: "manual". State-changing methods send a JSON body withContent-Type: application/json; the browser attaches theOriginheader the BFF checks for CSRF. The session cookie is HttpOnly andSameSite=Strict. NoAuthorizationheader is ever set. - Redirects and non-JSON success bodies are treated as invalid responses.
204resolves empty. - Error bodies are accepted as
{"error":"code"}(BFF) or{"error":{"code","request_id"}}(management).X-Request-Idis 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 and204handling; 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 withpnpm exec playwright test -c apps/console/playwright.config.ts. The config builds toapps/console/.wrangler/ui-test-distand serves it on port 5185.
Source: docs/api-spec/auth4d-20.md