| name | identityserver-configuration |
| description | Configure Duende IdentityServer including client definitions, API resources, identity resources, scopes, signing credentials, and server-side sessions. Covers client types (M2M, interactive, SPA), grant types, API Scopes vs API Resources vs Identity Resources, secret management, and client authentication methods. Includes both in-memory and database-backed configuration. |
| invocable | false |
Duende IdentityServer Configuration
When to Use This Skill
Use this skill when:
- Setting up a new Duende IdentityServer host
- Defining or modifying client registrations
- Configuring API resources, API scopes, or identity resources
- Setting up signing key management (automatic or static)
- Enabling server-side sessions
- Tuning
IdentityServerOptions for production deployments
- Migrating from IdentityServer4 to Duende IdentityServer
Core Principles
- Authorization Code + PKCE by Default โ Use
GrantTypes.Code for all interactive clients. Never use implicit flow for new applications.
- Least Privilege Scopes โ Grant clients only the scopes they need. Avoid wildcard or overly broad scope assignments.
- Automatic Key Management โ Prefer the built-in automatic key rotation over static key configuration in production.
- API Resources for Audience Isolation โ Use
ApiResource to control the aud claim and isolate API boundaries. Use ApiScope for fine-grained permission modeling within those boundaries.
- Server-Side Sessions for Enterprise โ Enable server-side sessions when you need centralized session management, back-channel logout, or session queries.
Related Skills
identityserver-stores โ EF Core persistence for configuration and operational data
oauth-oidc-protocols โ Protocol fundamentals that underpin these configuration choices
identity-security-hardening โ Production hardening of IdentityServer deployments
token-management โ Client-side token lifecycle with Duende.AccessTokenManagement
aspnetcore-authentication โ Configuring OIDC authentication in client applications
Docs: https://docs.duendesoftware.com/identityserver/configuration
Sub-Documents
Load these sub-documents when the user's question specifically targets one of these areas:
| Document | Description | When to Load |
|---|
| docs/client-types.md | Grant type selection matrix, client property reference tables, client authentication methods (shared secret, private_key_jwt, mTLS), secret rollover, and CORS | private_key_jwt, mTLS, secret rotation, refresh token settings, client authentication, CORS origins |
| docs/resources-scopes.md | Resource type decision matrix, identity resources, API scopes (including parameterized scopes), and API resources with audience isolation | aud claim, audience isolation, parameterized scopes, EmitStaticAudienceClaim, API Resources, Identity Resources |
Pattern 1: Hosting and Basic Setup
Register Duende IdentityServer in Program.cs with AddIdentityServer. All configuration flows from the IdentityServerOptions lambda and the builder's fluent API.
var builder = WebApplication.CreateBuilder(args);
var idsvrBuilder = builder.Services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
})
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients);
var app = builder.Build();
app.UseIdentityServer();
app.UseAuthorization();
app.Run();
Important: Call UseIdentityServer() instead of UseAuthentication() โ it registers both the IdentityServer middleware and the authentication middleware.
Pattern 2: Client Definitions
Clients represent applications that request tokens. The three most common configurations are:
Machine-to-Machine (Client Credentials)
For service-to-service communication with no interactive user:
new Client
{
ClientId = "service.worker",
ClientName = "Background Worker Service",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedScopes = { "api1", "api2.read_only" }
}
Interactive Web Application (Authorization Code + PKCE)
For server-rendered web apps that authenticate users and call APIs:
new Client
{
ClientId = "web.app",
ClientName = "Web Application",
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
ClientSecrets = { new Secret("secret".Sha256()) },
RedirectUris = { "https://app.example.com/signin-oidc" },
PostLogoutRedirectUris = { "https://app.example.com/signout-callback-oidc" },
FrontChannelLogoutUri = "https://app.example.com/signout-oidc",
AllowOfflineAccess = true,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"api1"
}
}
SPA with BFF Pattern
For JavaScript SPAs using the Backend-for-Frontend pattern (see duende-bff skill):
new Client
{
ClientId = "spa.bff",
ClientName = "SPA with BFF",
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
RequireClientSecret = true,
ClientSecrets = { new Secret("secret".Sha256()) },
RedirectUris = { "https://app.example.com/signin-oidc" },
PostLogoutRedirectUris = { "https://app.example.com/signout-callback-oidc" },
BackChannelLogoutUri = "https://app.example.com/bff/backchannel",
AllowOfflineAccess = true,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
}
Key Client Properties
| Property | Purpose | Default |
|---|
RequirePkce | Enforce PKCE for authorization code flow | true |
AllowOfflineAccess | Enable refresh token issuance | false |
AccessTokenLifetime | Access token duration in seconds | 3600 (1 hour) |
IdentityTokenLifetime | Identity token duration in seconds | 300 (5 min) |
RefreshTokenUsage | ReUse or OneTimeOnly | ReUse (recommend OneTimeOnly for security) |
RefreshTokenExpiration | Absolute or Sliding | Absolute |
AbsoluteRefreshTokenLifetime | Max refresh token lifetime in seconds | 2592000 (30 days) |
AllowedCorsOrigins | CORS origins for token endpoint calls | empty |
RequireConsent | Show consent screen | false |
CoordinateLifetimeWithUserSession | Tie token lifetimes to user session | false |
Defining Clients in appsettings.json
For scenarios where client configuration should be externalized:
{
"IdentityServer": {
"Clients": [
{
"Enabled": true,
"ClientId": "local-dev",
"ClientName": "Local Development",
"ClientSecrets": [
{
"Value": "<Insert Sha256 hash of the secret encoded as Base64 string>"
}
],
"AllowedGrantTypes": ["client_credentials"],
"AllowedScopes": ["api1"]
}
]
}
}
idsvrBuilder.AddInMemoryClients(
configuration.GetSection("IdentityServer:Clients"));
Pattern 3: Identity Resources
Identity resources define groups of claims about users, requested via the scope parameter. They map to claims in the identity token and the userinfo endpoint.
Standard Identity Resources
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResources.Phone(),
new IdentityResources.Address(),
};
Custom Identity Resources
Define custom identity resources for application-specific user claims:
new IdentityResource(
name: "tenant",
displayName: "Your organization info",
userClaims: new[] { "tenant_id", "tenant_name", "tenant_role" })
{
Required = true
}
Key concept: The openid scope is mandatory for any OpenID Connect request. It tells IdentityServer to return the sub (subject ID) claim.
Pattern 4: API Scopes and API Resources
API scopes and API resources work together to model your API surface area. Understanding the distinction is critical.
API Scopes โ Permission Model
An ApiScope represents a permission or capability a client can request:
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope("api1", "Main API"),
new ApiScope("catalog.read", "Read product catalog"),
new ApiScope("catalog.write", "Modify product catalog"),
new ApiScope("orders.manage", "Manage orders"),
new ApiScope("invoicing", "Invoicing API")
{
UserClaims = { "department", "cost_center" }
}
};
API Resources โ Logical API Boundaries
An ApiResource represents a logical API (typically a deployed service). It groups scopes and controls the aud (audience) claim in access tokens:
public static IEnumerable<ApiResource> ApiResources =>
new ApiResource[]
{
new ApiResource("catalog-api", "Product Catalog API")
{
Scopes = { "catalog.read", "catalog.write" },
UserClaims = { "role" }
},
new ApiResource("orders-api", "Order Management API")
{
Scopes = { "orders.manage" },
ApiSecrets = { new Secret("orders-secret".Sha256()) }
}
};
When to Use ApiResource vs ApiScope
| Scenario | Use ApiScope alone? | Add ApiResource? |
|---|
| Single API, simple permissions | โ
Sufficient | Optional |
| Multiple APIs sharing a scope | โ | โ
Required for audience isolation |
| Reference token introspection | โ | โ
Required for API secrets |
| Per-API signing algorithms | โ | โ
Use AllowedTokenSigningAlgorithms |
| Resource isolation (RFC 8707) | โ | โ
Required |
Resource Isolation
When multiple APIs share scope names, resource isolation prevents a token issued for one API from being used at another:
new ApiResource("inventory-api") { Scopes = { "read", "write" } },
new ApiResource("reporting-api") { Scopes = { "read" } }
With resource isolation, the client specifies the target resource in the token request using the resource parameter (RFC 8707), and IdentityServer issues a token with a single audience.
Pattern 5: Automatic Key Management
Duende IdentityServer's automatic key management handles signing key creation, rotation, and retirement. This is the recommended approach for production.
var idsvrBuilder = builder.Services.AddIdentityServer(options =>
{
options.KeyManagement.RotationInterval = TimeSpan.FromDays(90);
options.KeyManagement.PropagationTime = TimeSpan.FromDays(14);
options.KeyManagement.RetentionDuration = TimeSpan.FromDays(14);
options.KeyManagement.DeleteRetiredKeys = true;
options.KeyManagement.DataProtectKeys = true;
});
Key Lifecycle
Keys move through these phases:
- Announced โ Added to discovery but not used for signing (
PropagationTime duration)
- Active โ Used for signing tokens (until
RotationInterval is reached)
- Retired โ No longer signs tokens, but remains in discovery for validation (
RetentionDuration)
- Deleted โ Removed from discovery (if
DeleteRetiredKeys is true)
Multiple Signing Algorithms
Support multiple algorithms for different clients or APIs:
options.KeyManagement.SigningAlgorithms = new[]
{
new SigningAlgorithmOptions(SecurityAlgorithms.RsaSha256)
{
UseX509Certificate = true
},
new SigningAlgorithmOptions(SecurityAlgorithms.RsaSsaPssSha256),
new SigningAlgorithmOptions(SecurityAlgorithms.EcdsaSha256)
};
The first algorithm in the list becomes the default. Clients and API resources can override via AllowedTokenSigningAlgorithms.
Load-Balanced Deployments
For file-system key storage in load-balanced environments, all instances need access to the same key path:
options.KeyManagement.KeyPath = "/home/shared/keys";
Alternatively, use the EF Core operational store for database-backed key storage (see identityserver-stores).
Pattern 6: Static Key Configuration
When automatic key management is not available or you need explicit control:
var idsvrBuilder = builder.Services.AddIdentityServer(options =>
{
options.KeyManagement.Enabled = false;
});
var signingKey = LoadKeyFromVault();
idsvrBuilder.AddSigningCredential(signingKey, SecurityAlgorithms.RsaSha256);
Manual Key Rotation (Three-Phase Process)
Rotating static keys requires careful sequencing to avoid breaking token validation:
idsvrBuilder.AddSigningCredential(oldKey, SecurityAlgorithms.RsaSha256);
idsvrBuilder.AddValidationKey(newKey, SecurityAlgorithms.RsaSha256);
idsvrBuilder.AddSigningCredential(newKey, SecurityAlgorithms.RsaSha256);
idsvrBuilder.AddValidationKey(oldKey, SecurityAlgorithms.RsaSha256);
idsvrBuilder.AddSigningCredential(newKey, SecurityAlgorithms.RsaSha256);
Pattern 7: Server-Side Sessions
Server-side sessions store authentication session data in a server-side store instead of the cookie alone. This enables centralized session management, queries, and back-channel logout.
var idsvrBuilder = builder.Services.AddIdentityServer(options =>
{
options.ServerSideSessions.RemoveExpiredSessionsFrequency = TimeSpan.FromMinutes(10);
options.ServerSideSessions.ExpiredSessionsTriggerBackchannelLogout = true;
options.Authentication.CoordinateClientLifetimesWithUserSession = true;
})
.AddServerSideSessions();
Important: Server-side sessions are enabled by calling .AddServerSideSessions() on the IdentityServer builder โ there is no options.ServerSideSessions.Enabled property. Add this to the builder chain, not to options.
Session Expiration Options
| Option | Default | Purpose |
|---|
ServerSideSessions.RemoveExpiredSessionsFrequency | 10 min | Cleanup interval |
ServerSideSessions.RemoveExpiredSessions | true | Enable automatic cleanup |
ServerSideSessions.ExpiredSessionsTriggerBackchannelLogout | true | Notify clients on session expiry |
Tip: Combine server-side sessions with CoordinateClientLifetimesWithUserSession = true to ensure refresh tokens are revoked when a user's session ends.
Pattern 8: Important IdentityServerOptions
Events โ Enable for Production Monitoring
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
Authentication Cookie Settings
options.Authentication.CookieLifetime = TimeSpan.FromHours(10);
options.Authentication.CookieSlidingExpiration = false;
Caching (with Store Caching Enabled)
options.Caching.ClientStoreExpiration = TimeSpan.FromMinutes(15);
options.Caching.ResourceStoreExpiration = TimeSpan.FromMinutes(15);
Pushed Authorization Requests (PAR)
options.PushedAuthorization.Required = true;
DPoP (Demonstrating Proof-of-Possession)
options.DPoP.ValidationMode = DPoPTokenExpirationValidationMode.Nonce;
options.DPoP.ServerClockSkew = TimeSpan.FromMinutes(5);
Common Pitfalls
1. Missing openid Scope
new Client
{
AllowedScopes = { "profile", "api1" }
}
new Client
{
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
}
2. Mismatched Redirect URIs
RedirectUris = { "https://app.example.com/signin-oidc/" }
RedirectUris = { "https://app.example.com/signin-oidc" }
3. Using ApiScope When ApiResource Is Needed
public static IEnumerable<ApiScope> ApiScopes =>
new[] { new ApiScope("read"), new ApiScope("write") };
public static IEnumerable<ApiResource> ApiResources =>
new[]
{
new ApiResource("my-api")
{
Scopes = { "read", "write" }
}
};
4. Plaintext Client Secrets in Source Control
ClientSecrets = { new Secret("my-production-secret".Sha256()) }
ClientSecrets = { new Secret(configuration["Clients:Web:Secret"].Sha256()) }
5. Forgetting AllowOfflineAccess for Refresh Tokens
var client = new Client
{
AllowedGrantTypes = GrantTypes.Code,
AllowOfflineAccess = false,
AllowedScopes = { "openid", "api1" }
};
var client = new Client
{
AllowedGrantTypes = GrantTypes.Code,
AllowOfflineAccess = true,
AllowedScopes = { "openid", "api1" }
};
6. Not Setting IssuerUri Behind a Reverse Proxy
options.IssuerUri = "https://identity.example.com";
Production Configuration Checklist
| Setting | Dev | Production |
|---|
KeyManagement.Enabled | true | true |
KeyManagement.DataProtectKeys | true | true + configure Data Protection |
Events.Raise*Events | Optional | All true |
ServerSideSessions (AddServerSideSessions()) | Optional | Recommended |
| Secrets | In-memory / config | Key vault / certificates |
| Store | In-memory | EF Core or custom |
| HTTPS | Optional | Required |
Resources