Skip to content

Logout

AuthProxy exposes a well-known logout endpoint that ends the current session and returns the user to a validated destination:

GET /.cratis/logout?redirect=<absolute-url>

The endpoint is anonymous — it works even when the session is already invalid — and is handled before the authentication challenge stages, so it never bounces the user to a login provider.


A plain cookie logout leaves the user signed in at the identity provider, so the next visit silently re-authenticates. To avoid that, AuthProxy performs a full-chain logout: it ends the session both locally and at the identity provider using OIDC RP-initiated logout.

When the session was established through an OIDC provider, a logout request:

  1. Reads the stored id_token and the provider the session was established with from the authentication cookie, and clears the local session (see What it clears).

  2. Redirects (302 Found) the browser to that provider’s end-session endpoint (discovered from the provider’s OpenID configuration) with:

    • id_token_hint — the stored id_token, so the provider knows which session to end.
    • post_logout_redirect_uri — AuthProxy’s own callback, /.cratis/logout/callback.

    The validated final redirect target is carried across the round-trip in a short-lived, HTTP-only cookie (.cratis-logout) rather than in the URL.

  3. The identity provider ends its own session and redirects back to /.cratis/logout/callback.

  4. The callback clears every AuthProxy cookie again (idempotent) and redirects (302 Found) to the validated final redirect target.

Register the callback with each OIDC provider. post_logout_redirect_uri must be allow-listed at the provider, so register https://<your-proxy-host>/.cratis/logout/callback as a permitted post-logout redirect URI for every OIDC application.

OAuth 2.0 providers have no standard OIDC end-session endpoint and cannot be force-logged-out via a redirect. When the session was established through an OAuth provider — or when there is no active OIDC session, or the provider’s discovery document advertises no end-session endpoint — AuthProxy falls back to a local-only logout: it clears its own cookies and redirects straight to the validated redirect target. The user’s session at the OAuth provider is left untouched, so a later visit may still re-authenticate silently without asking for credentials. This is a limitation of the OAuth providers, not of AuthProxy.


Both the local logout and the post-logout callback:

  1. Sign the user out of the authentication cookie (.Cratis.AuthProxy.Auth.v2), including any chunked variants.
  2. Delete every AuthProxy session cookie:
    • .cratis-identity
    • .cratis-identity-authorization
    • .cratis-tenant
    • .cratis-tenants
    • .cratis-invite
    • .cratis-invite-state
    • .cratis-registration
    • .cratis-providers
  3. Delete every transient sign-in handshake cookie the browser sent (.AspNetCore.Correlation.*, .AspNetCore.OpenIdConnect.Nonce.*) — the leftovers of abandoned handshakes that would otherwise poison the next sign-in. See Failed Sign-ins.
  4. Delete any additional cookies the deployment configured under Cratis:AuthProxy:Logout:AdditionalCookies — cookies AuthProxy does not issue itself but that must not survive a logout. See Clearing additional cookies.

The .cratis-logout carry cookie is deleted by the callback once the final target has been read from it.

After logout nothing of the session survives, so the next visit to a protected resource lands on the provider-selection page (or the single provider’s login) rather than silently reusing anything local. Whether the identity provider still remembers the user is governed by the full-chain behavior above.


The post-logout destination is supplied as an absolute URL in the redirect query-string parameter, for example:

/.cratis/logout?redirect=https://cratis.studio

Because the target is absolute, it cannot be validated with the relative-URL check used elsewhere. Instead it is matched against an allow-list of origins so neither the endpoint nor its callback can be turned into an open redirect. The target is validated on both legs of the round-trip. A target is allowed when its origin (scheme + host + port) matches any of:

  • The proxy’s own public origin as seen by the browser (derived from the request, honoring X-Forwarded-Proto). This covers redirecting back to the site the user is already on.
  • Any configured service frontend (Cratis:AuthProxy:Services:<name>:Frontend:BaseUrl).
  • The configured lobby frontend (Cratis:AuthProxy:Invite:Lobby:Frontend:BaseUrl).
  • Any origin listed in Cratis:AuthProxy:Logout:AllowedRedirectOrigins (see below).

Same-site relative URLs (a single leading /, but not //) are always allowed.

If redirect is missing, empty, or fails validation, AuthProxy falls back to the application root (/).


The implicit origins above cover redirecting back to the app itself, but a deployment often wants to send the user somewhere else after logout — for example a separate marketing or landing site that is neither the proxy’s own origin nor a configured frontend. List those origins under Cratis:AuthProxy:Logout:AllowedRedirectOrigins. Each entry is an absolute origin (scheme + host, optionally a port) with no path; malformed or non-HTTP(S) entries are ignored.

{
"Cratis": {
"AuthProxy": {
"Logout": {
"AllowedRedirectOrigins": [
"https://cratis.studio"
]
}
}
}
}

Equivalent environment variables (one indexed key per entry):

Cratis__AuthProxy__Logout__AllowedRedirectOrigins__0=https://cratis.studio

With the example above, GET /.cratis/logout?redirect=https://cratis.studio is permitted even when the app itself is served from a different host such as https://app.cratis.studio.


Logout clears every cookie AuthProxy issues — but a deployment often runs sibling authentication infrastructure whose cookies AuthProxy knows nothing about. The classic case is a separate oauth2-proxy guarding an admin app whose session cookie was scoped to the parent domain (e.g. _oauth2_proxy_admin on .cratis.studio): every browser that ever signed in there sends it on every *.cratis.studio request, and it survives AuthProxy’s logout — leaving the user half-signed-in from the application’s point of view.

List such cookies under Cratis:AuthProxy:Logout:AdditionalCookies and the session-termination sweep deletes them too. Each entry has:

  • Name (required) — the exact cookie name. Matching is by exact name only; there is no prefix or wildcard support.
  • Domain (optional) — the domain the cookie was scoped to, e.g. .cratis.studio. When set, the deletion is issued for that domain as well as the request host — necessary because a host-scoped deletion cannot touch a parent-domain cookie. Deleting for a parent domain of the current host is legal, which is exactly what makes this work. Leave it out for a cookie scoped to the request host itself.

Deletions are issued at the root path (Path=/) with the Secure attribute mirroring the request scheme, so they reliably match how such cookies are typically written.

{
"Cratis": {
"AuthProxy": {
"Logout": {
"AdditionalCookies": [
{
"Name": "_oauth2_proxy_admin",
"Domain": ".cratis.studio"
}
]
}
}
}
}

Equivalent environment variables (one indexed entry per cookie):

Cratis__AuthProxy__Logout__AdditionalCookies__0__Name=_oauth2_proxy_admin
Cratis__AuthProxy__Logout__AdditionalCookies__0__Domain=.cratis.studio

The configured cookies are cleared everywhere the session is terminated:

  • the logout endpoint (both legs of a full-chain logout), and
  • the sign-in sweep that runs when a provider callback completes — so a fresh sign-in also heals a browser still carrying the stale cookie, without requiring the user to log out first.

This is a cleanup mechanism for cookies that linger after the owning deployment has been fixed (for example, until an old parent-domain cookie expires) — it does not log the user out of the system that issued the cookie.


A “Log out” control in the application simply navigates the browser to the endpoint:

<a href="/cratis/logout/?redirect=https://cratis.studio">Log out</a>

For an OIDC session the browser is taken through the provider’s end-session endpoint and back via the callback; for an OAuth session it lands on the target directly. Either way the user ends up on the target unauthenticated at AuthProxy, and requesting a protected resource then triggers the normal login flow.