Add session-preserving credential-linking endpoint - #60
Merged
Conversation
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>
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>
Add a GET /.cratis/link/{scheme} flow that authenticates a second
identity-provider login for an already signed-in user and posts the
freshly authenticated subject to the application, without ever writing
the primary authentication cookie — so the user's session is preserved.
- LinkMiddleware initiates the challenge in link mode (requires an
authenticated user; validates scheme and one-time token; constrains
returnUrl to a same-site relative path).
- The shared OnTicketReceived handler captures the subject on the
provider callback and short-circuits before sign-in, handing it to
LinkSubjectExchanger, which POSTs { subject, identityProvider } to the
configured Link.ExchangeUrl with the link token as the bearer
credential — mirroring the invite exchange.
Refs Cratis/Studio#862
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cover LinkMiddleware initiation (challenge in link mode, carrying the token; 404/401/400 rejections; open-redirect-safe returnUrl fallback) and LinkSubjectExchanger (posts the subject with the link token as bearer; fails when unconfigured, tokenless, or on endpoint error). Refs Cratis/Studio#862 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Refs Cratis/Studio#862 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the AuthProxy half of the "add a credential" proof-of-control flow for Cratis/Studio#862: a session-preserving link challenge that authenticates a second identity-provider login for an already signed-in user and hands the freshly authenticated subject to the application — without replacing the primary session.
Added
GET /.cratis/link/{scheme}— a session-preserving credential-linking challenge. It authenticates the requested provider for an already signed-in user but, on the provider callback, captures the authenticatedsubjectand posts it to the application instead of signing the new identity in, so the user's primary session is preserved. The one-time link token and a same-site-onlyreturnUrltravel through the flow. (Cratis/Studio#862)Cratis:AuthProxy:Link:ExchangeUrlconfiguration — the application back-channel endpoint AuthProxy posts{ subject, identityProvider }to (with the link token as the bearer credential), the link counterpart ofCratis:AuthProxy:Invite:ExchangeUrl. (Cratis/Studio#862)Security
401), validates the provider scheme (unknown →404) and requires a one-time token (missing →400).returnUrlis constrained to a same-site relative path so the endpoint can never be turned into an open redirect. The subject is delivered to the application server-to-server from a real provider authentication — never trusted from client input. (Cratis/Studio#862)Notes for review
X-Frame-Options: DENYand AuthProxy cookies areSameSite=Lax, so the flow is a popup / top-level redirect, as agreed in the issue's posted design.OnTicketReceivedwithcontext.HandleResponse()before theRemoteAuthenticationHandlersigns the ticket into the cookie scheme (the "capturesub/issand short-circuit" option from the design), rather than a throwaway sign-in scheme.prompt=select_account, so a provider with an active SSO session may silently return the same account. Left out because prompt handling differs across OIDC/OAuth and GitHub has no standard equivalent; flagging for review — it can be added if linking a different account of the same provider must be guaranteed.🤖 Generated with Claude Code