diff --git a/astro/src/content/docs/identityserver/spaces/index.mdx b/astro/src/content/docs/identityserver/spaces/index.mdx index 71dab5864..11b95a203 100644 --- a/astro/src/content/docs/identityserver/spaces/index.mdx +++ b/astro/src/content/docs/identityserver/spaces/index.mdx @@ -1,7 +1,7 @@ --- title: "IdentityServer Spaces" description: "Run multiple isolated IdentityServer security domains in a single deployment using Spaces with per-tenant configuration and operational data" -date: 2026-10-23 +date: 2026-11-02 sidebar: label: "Overview" order: 1 @@ -98,6 +98,62 @@ If users must be shared across spaces, use a deliberately separate user director [space-aware User Management storage](/identityserver/data/providers/duende-storage/getting-started.md#use-duende-user-management). ::: +## Cookies and Space Paths + +When a path pattern matches a space, IdentityServer scopes its cookies to that space's path. This keeps sessions in +different spaces on the same origin apart. IdentityServer derives the cookie `Path` on every request from the resolved +base path, which is the hosting `PathBase` plus the matched space path (for example, `/host/space-a`). Spaces matched +by origin only add no space path, so their cookies use the hosting `PathBase`, or `/` when it is empty. +There is nothing to configure. All other cookie attributes (name, `Domain`, `Secure`, `SameSite`, and `HttpOnly`) come +from your configuration unchanged. + +IdentityServer scopes these cookies to the space path: + +* The session cookie, which uses the `idsrv` scheme by default. If you set + `IdentityServerOptions.Authentication.CookieAuthenticationScheme`, or rely on the default authenticate or default + scheme, that scheme's cookie is scoped instead (for example, ASP.NET Core Identity's `Identity.Application`). +* The `idsrv.external` cookie used for external authentication. +* The temporary message cookies that carry state between pages, such as consent. +* The check-session cookie, `idsrv.session` by default. It takes the same path as the cookies above, so it is scoped to + the space path and your hosting `PathBase`. The name comes from `Authentication.CheckSessionCookieName`. IdentityServer + issues it only when `Endpoints.EnableCheckSessionEndpoint` is enabled. It still uses `Domain` and `SameSite` from + `Authentication.CheckSessionCookieDomain` and `Authentication.CheckSessionCookieSameSiteMode`. + +Other application cookies, and the correlation and nonce cookies of external authentication handlers such as OpenID +Connect, are not changed. + +:::caution[Don't use the __Host- cookie name prefix with a non-root cookie path] +The `__Host-` prefix requires `Path=/`. If IdentityServer builds a `__Host-` prefixed cookie for a request whose +resolved cookie path isn't `/`, it throws an `InvalidOperationException` before issuing the cookie. This happens with +path-based spaces, and also with origin-only spaces or apps without Spaces when the hosting `PathBase` isn't empty. +The message starts with: `Cookie '' uses the '__Host-' prefix, which requires the cookie path to be '/', but the +resolved IdentityServer cookie path is ''.` Use the `__Secure-` prefix instead. It still requires the `Secure` +attribute but permits non-root paths. +::: + +### Rollout Impact + +IdentityServer doesn't migrate or remove existing cookies. Cookies issued with `Path=/` before you enabled path-based +spaces stay in the browser until they expire or the user clears them. Browsers send `Path=/` cookies to every path, +so these older cookies are still sent to requests under each space path. A new space-scoped cookie with the same name +can exist next to them. + +Changing the cookie path alone doesn't end existing sessions. Whether IdentityServer accepts an older cookie depends on +[data protection partitioning](/identityserver/spaces/getting-started.md#before-deployment): + +* In a named space, the partitioned data protector can't read a cookie that was protected before partitioning, so the + user has to sign in again. +* In the Default space, a fallback to the unpartitioned purpose chain can still read the + older cookie, so the existing session can stay valid. +* With `SpacesOptions.PartitionDataProtection` set to `false`, an older cookie can be read in any space. + +Don't rely on path scoping to sign users out. If existing sessions must end when you enable path-based spaces, end them +explicitly, for example by changing the session cookie name. Hosts without a space path, including origin-only spaces +with an empty `PathBase`, keep `Path=/` and aren't affected. + +Call `UseSpaceResolution()` before `UseIdentityServer()`. If space resolution hasn't run yet, cookies fall back to +`Path=/` and aren't isolated between spaces. + ## What Spaces Does Not Isolate :::caution[Spaces Do Not Isolate Every Process-Level Dependency]