Skip to content
New Livestream: How Banks Protect Their Apps with FAPI 2.0. Register Now!

Getting Started With Spaces

Spaces require Duende Storage for their management data and isolated storage pools.

This example uses SQLite:

Terminal window
# Terminal
dotnet add package Duende.IdentityServer --prerelease
dotnet add package Duende.Storage.Sqlite --prerelease
dotnet add package Duende.Spaces --prerelease

See the Duende.Spaces NuGet Gallery page for package versions.

Add a connection string for the SQLite database:

appsettings.json
{
"ConnectionStrings": {
"IdentityServer": "Data Source=identityserver.db"
}
}

Keep production credentials out of source control and load them from your deployment platform’s secret store.

Register Spaces and the IdentityServer storage adapters. AddStorage registers both configuration and operational storage in one call:

Program.cs
using System.Net;
using Duende.IdentityServer;
using Duende.Spaces;
using Duende.Storage.Schema;
using Duende.Storage.Sqlite;
using Microsoft.AspNetCore.HttpOverrides;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddIdentityServer()
.AddServerSideSessions()
.AddStorage(storage =>
storage.AddSqliteStore(options =>
options.ConnectionString =
builder.Configuration.GetConnectionString("IdentityServer")
?? throw new InvalidOperationException(
"IdentityServer connection string is missing.")));
builder.Services.AddSpaces();
builder.Services.Configure<SpacesOptions>(options =>
{
options.SpacePathPrefix = "/t";
options.FallbackToDefault = false;
});
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedHost |
ForwardedHeaders.XForwardedProto;
options.KnownProxies.Add(IPAddress.Parse("203.0.113.42"));
options.ForwardLimit = 1;
});
var app = builder.Build();
await app.Services
.GetRequiredService<IDatabaseSchema>()
.MigrateAsync(CancellationToken.None);

FallbackToDefault is already false; setting it explicitly makes the intended isolation behavior visible during review.

Use ISpaceAdmin from a trusted provisioning or administration path. Query by name before creating the space:

Program.cs
using Duende.Storage.Querying;
var spaces = app.Services.GetRequiredService<ISpaceAdmin>();
var existing = await spaces.QueryAsync(
QueryRequest.Create<SpaceFilter, SpaceSortField>(
new SpaceFilter { Name = "Acme" }),
CancellationToken.None);
if (!existing.Items.Any(space =>
string.Equals(space.Name, "Acme", StringComparison.Ordinal)))
{
var result = await spaces.CreateAsync(
new CreateSpaceConfiguration
{
Name = "Acme",
MatchPatterns =
[
new SpaceMatchPattern
{
Origin = "https://login.example.com",
Path = "/acme"
}
]
},
CancellationToken.None);
if (!result.IsSuccess)
{
throw new InvalidOperationException(
$"Could not create the Acme space: {result.Errors}");
}
}

The Acme space created in this example requires both the origin and path to match. A request to https://login.example.com/t/acme/.well-known/openid-configuration resolves to the Acme pool.

Forwarded headers must run before space resolution so the resolver sees the public scheme and host. Space resolution must then run before ASP.NET Core routing because path-based matches rewrite PathBase and Path:

Program.cs
app.UseForwardedHeaders();
app.UseSpaceResolution();
app.UseRouting();
app.UseIdentityServer();
app.Run();

If another middleware reads tenant-specific data, place it after UseSpaceResolution. You can inject ISpaceContextAccessor into scoped services and call GetSpaceId() after resolution.

Replace 203.0.113.42 with your proxy’s address or configure an appropriate trusted network. Only accept forwarded headers from expected proxies or networks. An untrusted forwarded host or protocol can otherwise influence which security boundary the request selects. See Proxy Servers and Load Balancers for more configuration options.

  • Use origin matching when each space has a dedicated host name.
  • Use path matching when spaces share a host. The default /t prefix keeps space paths separate from ordinary routes.
  • Use both when a space must be constrained to a specific host and path.

Origins must include the scheme and host, plus the port when it is not the scheme default.

Review what Spaces does not isolate before deploying. Confirm whether signing credentials, Data Protection, caches, telemetry, rate limits and custom services require space-aware configuration.

For a complete, runnable multi-space deployment, see the Spaces sample.