Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions astro/src/content/docs/identityserver/spaces/data-protection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
title: "Data Protection In Spaces"
description: "How Spaces partitions ASP.NET Core Data Protection by space, including the Default space fallback, failures outside a resolved space, and which providers are partitioned."
date: 2026-11-02
sidebar:
label: "Data Protection"
order: 20
---

Spaces partitions [ASP.NET Core Data Protection](/general/data-protection.md) by space when
`SpacesOptions.PartitionDataProtection` is `true`, which is the default. A protector created from the app's
`IDataProtectionProvider` binds to the space that is resolved when it protects or unprotects a value. A value protected
in one space can't be unprotected in another. Use `GetUnpartitioned()` on the provider for data that must be readable
from every space.

This page describes how partitioning behaves at runtime and where its limits are. To turn partitioning on or off, see
[Getting Started With Spaces](/identityserver/spaces/getting-started.md#before-deployment).

## Protecting Data Outside A Resolved Space

If `IDataProtector.Protect` or `IDataProtector.Unprotect` runs with no resolved space, Spaces throws an
`InvalidOperationException` instead of falling back to a shared key. The message starts with "Data protection was used
with no resolved space while SpacesOptions.PartitionDataProtection is enabled."

To avoid this:

* Call `UseSpaceResolution()` before any middleware that uses Data Protection.
* Run background work inside `ISpaceContextAccessor.SetSpace(...)`.

## Default Space Fallback

Values protected before you enabled partitioning use the unpartitioned purpose chain. To keep those values readable,
Spaces retries a failed unprotect in the Default space:

* When unprotecting fails with a `CryptographicException` in the Default space, Spaces retries with the unpartitioned
purpose chain.
* The fallback only applies to reads. Spaces always protects new values with the partitioned purpose chain.
* The fallback never applies to the Management space or to named spaces.

Each successful fallback increments the `duende.spaces.data_protection.default_space_fallback` counter on the
`Duende.Spaces` meter. Use it to see whether values protected before partitioning are still being read.

Never use the same purpose chain through both `GetUnpartitioned()` and the partitioned provider. In the Default space,
the fallback would accept the unpartitioned value. Spaces doesn't check for this.

## Which Providers Are Partitioned

Spaces only partitions the non-keyed `IDataProtectionProvider` registration. These are not partitioned:

* Keyed `IDataProtectionProvider` registrations.
* Providers assigned to an authentication scheme's options, such as
`CookieAuthenticationOptions.DataProtectionProvider`.
Comment thread
maartenba marked this conversation as resolved.
Resolved

Register the provider once, as a singleton, before you call `AddSpaces`. `UseSpaceResolution()` throws at startup when
the provider was registered or replaced after `AddSpaces`, or when more than one provider is registered.
10 changes: 9 additions & 1 deletion astro/src/content/docs/identityserver/spaces/getting-started.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Getting Started With Spaces"
description: "Step-by-step guide to installing Duende.Spaces, configuring space resolution by origin or path and creating your first IdentityServer space"
date: 2026-10-23
date: 2026-11-02
sidebar:
label: "Getting Started"
order: 10
Expand Down Expand Up @@ -189,6 +189,9 @@ unprotected in another. IdentityServer's own signing key protection stays global
`GetUnpartitioned()` on the provider. Use `GetUnpartitioned()` yourself for data that must be readable from every
space; see [Data Protection](/general/data-protection.md) for background on providers and protectors.

For the Default space fallback, failures outside a resolved space, and which providers are partitioned, see
[Data Protection In Spaces](/identityserver/spaces/data-protection.md).

Set `SpacesOptions.PartitionDataProtection` to `false` when all protected data from the app's
`IDataProtectionProvider`, such as cookies or tokens, must be readable across spaces. This removes per-space
partitioning for that data. If only specific values need to be shared, use `GetUnpartitioned()` for those protectors
Expand All @@ -199,6 +202,11 @@ sharing the app's default `HybridCache`. If your app already relies on an unkeye
call `AddHybridCache()` yourself. Entries created through `IHybridCacheFactory` are automatically partitioned by the
resolved space, so callers don't need to build space-aware cache keys themselves.

Space resolution results are cached in the Spaces `HybridCache`. `SpacesOptions.LocalCacheExpiration` sets the
in-process cache lifetime (default 30 seconds), and `SpacesOptions.Expiration` sets the distributed cache lifetime
(default 30 minutes). A space change is visible immediately on the instance that made it. Other instances can take up to
these lifetimes to see it.

Spaces management data and every space's pool share the default Duende Storage instance unless you map them elsewhere.
See [Multiple Storage Instances](/identityserver/data/providers/duende-storage/multiple-storage-instances.md) to route
IdentityServer's configuration or operational data, or User Management's data, to a separate database from Spaces
Expand Down
Loading