AUTH4D-111 hosted console sign-in screen

The developer console signs in through the hosted login app instead of an inline page rendered by the API. The email-code endpoints from AUTH4D-17 are unchanged apart from the resend behavior below.

Authorize

GET {AUTH_ORIGIN}/management-auth/authorize?response_type=code&client_id=cli_...&...

HTTP/1.1 302 Found
Location: {AUTH_ORIGIN}/login/console?flow_id=flw_<32 base64url characters>
Set-Cookie: __Host-auth4_control_flow=...; Path=/; Max-Age=600; HttpOnly; Secure; SameSite=Lax
Cache-Control: no-store, max-age=0
Referrer-Policy: no-referrer

Validation and error responses are unchanged: a malformed request returns the JSON protocol error (400 invalid_request, or 503 temporarily_unavailable) and sets no cookie. The response has no body, so the API no longer sends a page that needs 'unsafe-inline' in its CSP.

The flow ID is not a credential. /management-auth/email/start and /management-auth/email/confirm still require the HttpOnly flow cookie, which reaches them because /login/* is served from the same API origin.

Hosted screen

GET {AUTH_ORIGIN}/login/console is a static copy of the hosted-login shell under the same strict CSP as the other hosted-login routes (script-src 'self'; style-src 'self'). The screen:

  • accepts exactly one flow_id matching ^flw_[A-Za-z0-9_-]{32}$ and otherwise shows “This sign-in link is not valid” without calling the API;
  • uses the shared email and code steps (inline validation, busy states, a polite status region and an alert region, resend with a countdown);
  • says “If the address can sign in, a code has been sent.” rather than confirming the address;
  • shows the same “not valid” screen when the API answers 403 access_denied, which means the flow expired, was used, or the cookie is missing;
  • maps every error code through describeError(), so server text is never shown;
  • navigates to the redirect_to URL returned by /management-auth/email/confirm.

Resend

POST /management-auth/email/start now replaces an outstanding code for the flow, so only the most recent code works. Previously a second request within ten minutes returned 202 without sending anything. Sends stay limited by the per-address and per-IP budgets, and the per-flow attempt budget is not reset by a resend.

Tests

  • tests/integration/auth4d-17.management-auth.test.ts (redirect, cookie, resend).
  • tests/e2e/auth4d-111.console-sign-in.spec.ts (Playwright, port 4311): end-to-end console sign-in through the new screen against the built hosted login, plus the invalid and expired link screens.

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