Skip to content

Add /.cratis/logout and persist the tenant list for the post-login switcher - #59

Merged
einari merged 4 commits into
mainfrom
feature/logout-and-persistent-tenant-switch
Jul 23, 2026
Merged

Add /.cratis/logout and persist the tenant list for the post-login switcher#59
einari merged 4 commits into
mainfrom
feature/logout-and-persistent-tenant-switch

Conversation

@einari

@einari einari commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Adds the AuthProxy pieces required by Studio's "log out and switch tenant" feature (Cratis/Studio#856). The Studio frontend is already live, but the flow was broken end-to-end because AuthProxy had no logout endpoint and never kept the tenant list available to the post-login toolbar switcher.

Added

  • GET /.cratis/logout — signs the user out of the authentication cookie and clears every AuthProxy session cookie (.cratis-identity, .cratis-tenant, .cratis-tenants, .cratis-invite, .cratis-registration, .cratis-providers), then redirects to the absolute URL in the redirect query parameter. The target is validated against an allow-list of origins (the proxy's own public origin plus configured service/lobby frontends) so it cannot be used as an open redirect; a missing or disallowed target falls back to the application root. The endpoint is anonymous and runs before the authentication-challenge stages, so it works without a valid session.

Changed

  • .cratis-tenants now persists for the post-login tenant switcher. It is written as a session cookie and is no longer deleted when a tenant is selected, so a user with more than one tenant keeps the list available for an in-app switch control for the rest of the browser session. Single-tenant users still have the cookie removed on auto-selection (no switcher), and every selection continues to re-validate membership against the tenants endpoint.

Closes the AuthProxy side of Cratis/Studio#856.

einari and others added 3 commits July 23, 2026 12:14
Introduce a LogoutMiddleware handling the well-known GET /.cratis/logout
path. It signs the user out of the authentication cookie and deletes every
AuthProxy session cookie (.cratis-identity, .cratis-tenant, .cratis-tenants,
.cratis-invite, .cratis-registration, .cratis-providers), then redirects to
the absolute URL supplied in the `redirect` query parameter.

The redirect target is validated against an allow-list of origins — the
proxy's own public origin (honoring forwarded headers) plus configured
service and lobby frontends — so the endpoint cannot be abused as an open
redirect; a missing or disallowed target falls back to the application root.

The middleware is registered before the authentication-challenge stages so
logout works without a valid session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Write .cratis-tenants as a session cookie and stop deleting it when a tenant
is selected, so a user with more than one tenant keeps the list available for
an in-app tenant switcher for the rest of the browser session. Single-tenant
users still have the cookie removed on auto-selection, so no switcher is shown
for them. Every selection continues to re-validate membership against the
tenants endpoint, so a stale cookie cannot grant access to a non-member tenant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a Logout configuration page describing /.cratis/logout — the cookies it
clears, the `redirect` parameter and its origin allow-list — and wire it into
the configuration TOC. Extend the tenant-selection page with a section on
switching tenants after selection via the retained .cratis-tenants cookie.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@einari einari added the minor label Jul 23, 2026
Add Cratis:AuthProxy:Logout:AllowedRedirectOrigins so a deployment can permit
post-logout redirect targets that are neither the proxy's own origin nor a
configured service or lobby frontend — for example a separate marketing site
served from a different host. Configured entries are normalized to their origin
and compared case-insensitively alongside the existing implicit allow-list;
malformed or non-HTTP(S) entries are ignored. A target matching nothing still
falls back to the application root.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@einari
einari merged commit d4b0e48 into main Jul 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant