Identityserver Configuration

作者 DuendeSoftwarefb32edc51982无许可证9 个星标收录于 2026年10月8日更新于 2026年10月8日仓库4周前更新

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.

AI 生成的概览

指导配置 Duende IdentityServer 的客户端、资源、作用域、签名密钥和服务端会话。

功能
该技能为配置 Duende IdentityServer 主机提供参考指南和代码模式。内容涵盖机器对机器、交互式 Web 以及 SPA/BFF 场景的客户端注册、身份资源、API 作用域与 API 资源的区别、自动与静态签名密钥管理,以及服务端会话。它还列出了常见陷阱和生产环境配置检查清单。
适用场景
适用于搭建新的 Duende IdentityServer 主机,或修改客户端注册、资源、作用域和签名密钥。也用于启用服务端会话、为生产环境调整 IdentityServerOptions,或从 IdentityServer4 迁移。
运行要求
无脚本,仅为说明性内容。所述配置面向使用 Duende IdentityServer 库的 .NET 应用,部分模式引用了外部文档和配套技能。

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

  1. Authorization Code + PKCE by Default — Use GrantTypes.Code for all interactive clients. Never use implicit flow for new applications.
  2. Least Privilege Scopes — Grant clients only the scopes they need. Avoid wildcard or overly broad scope assignments.
  3. Automatic Key Management — Prefer the built-in automatic key rotation over static key configuration in production.
  4. 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.
  5. 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:

DocumentDescriptionWhen to Load
docs/client-types.md [blocked]Grant type selection matrix, client property reference tables, client authentication methods (shared secret, private_key_jwt, mTLS), secret rollover, and CORSprivate_key_jwt, mTLS, secret rotation, refresh token settings, client authentication, CORS origins
docs/resources-scopes.md [blocked]Resource type decision matrix, identity resources, API scopes (including parameterized scopes), and API resources with audience isolationaud claim, audience isolation, parameterized scopes, IScopeParser, IResourceValidator, 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.

csharp
var builder = WebApplication.CreateBuilder(args);
var idsvrBuilder = builder.Services.AddIdentityServer(options =>{    // Let the issuer be inferred from the request URL (recommended)    // options.IssuerUri = "https://identity.example.com"; // Only set when behind a reverse proxy
    // Enable events for diagnostics    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(); // Includes UseAuthentication()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:

csharp
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:

csharp
new Client{    ClientId = "web.app",    ClientName = "Web Application",
    AllowedGrantTypes = GrantTypes.Code,    RequirePkce = true, // Default is true in Duende IS
    ClientSecrets = { new Secret("secret".Sha256()) },
    // Redirect URIs — must exactly match what the client sends    RedirectUris = { "https://app.example.com/signin-oidc" },    PostLogoutRedirectUris = { "https://app.example.com/signout-callback-oidc" },    FrontChannelLogoutUri = "https://app.example.com/signout-oidc",
    // Enable offline access for refresh tokens    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):

csharp
new Client{    ClientId = "spa.bff",    ClientName = "SPA with BFF",
    AllowedGrantTypes = GrantTypes.Code,    RequirePkce = true,    RequireClientSecret = true, // BFF host holds the secret
    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

PropertyPurposeDefault
RequirePkceEnforce PKCE for authorization code flowtrue
AllowOfflineAccessEnable refresh token issuancefalse
AccessTokenLifetimeAccess token duration in seconds3600 (1 hour)
IdentityTokenLifetimeIdentity token duration in seconds300 (5 min)
RefreshTokenUsageReUse or OneTimeOnlyReUse (recommend OneTimeOnly for security)
RefreshTokenExpirationAbsolute or SlidingAbsolute
AbsoluteRefreshTokenLifetimeMax refresh token lifetime in seconds2592000 (30 days)
AllowedCorsOriginsCORS origins for token endpoint callsempty
RequireConsentShow consent screenfalse
CoordinateLifetimeWithUserSessionTie token lifetimes to user sessionfalse

Defining Clients in appsettings.json

For scenarios where client configuration should be externalized:

json
{  "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"]      }    ]  }}
csharp
// Load clients from configurationidsvrBuilder.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

csharp
public static IEnumerable<IdentityResource> IdentityResources =>    new IdentityResource[]    {        new IdentityResources.OpenId(),   // Required — maps to "sub" claim        new IdentityResources.Profile(),  // name, family_name, given_name, etc.        new IdentityResources.Email(),    // email, email_verified        new IdentityResources.Phone(),    // phone_number, phone_number_verified        new IdentityResources.Address(),  // address (JSON object)    };

Custom Identity Resources

Define custom identity resources for application-specific user claims:

csharp
// ✅ Custom identity resource for tenant membershipnew IdentityResource(    name: "tenant",    displayName: "Your organization info",    userClaims: new[] { "tenant_id", "tenant_name", "tenant_role" }){    Required = true // Do not show on consent screen as optional}

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:

csharp
public static IEnumerable<ApiScope> ApiScopes =>    new ApiScope[]    {        // Simple scope — just a name        new ApiScope("api1", "Main API"),
        // Granular scopes for fine-grained access        new ApiScope("catalog.read", "Read product catalog"),        new ApiScope("catalog.write", "Modify product catalog"),        new ApiScope("orders.manage", "Manage orders"),
        // Scope that includes specific user claims in the access token        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:

csharp
public static IEnumerable<ApiResource> ApiResources =>    new ApiResource[]    {        new ApiResource("catalog-api", "Product Catalog API")        {            Scopes = { "catalog.read", "catalog.write" },
            // These claims are included when any scope in this resource is requested            UserClaims = { "role" }        },        new ApiResource("orders-api", "Order Management API")        {            Scopes = { "orders.manage" },
            // API-specific secret for reference token introspection            ApiSecrets = { new Secret("orders-secret".Sha256()) }        }    };

When to Use ApiResource vs ApiScope

ScenarioUse ApiScope alone?Add ApiResource?
Single API, simple permissions✅ SufficientOptional
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:

csharp
// Two separate APIs that both have a "read" scopenew 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.

csharp
var idsvrBuilder = builder.Services.AddIdentityServer(options =>{    // Automatic key management is enabled by default    // Customize rotation policy:    options.KeyManagement.RotationInterval = TimeSpan.FromDays(90);    // New key every 90 days    options.KeyManagement.PropagationTime = TimeSpan.FromDays(14);     // Announce 14 days early    options.KeyManagement.RetentionDuration = TimeSpan.FromDays(14);   // Keep old keys 14 days    options.KeyManagement.DeleteRetiredKeys = true;                    // Clean up old keys
    // Keys are encrypted at rest via ASP.NET Data Protection (default: true)    options.KeyManagement.DataProtectKeys = true;});

Key Lifecycle

Keys move through these phases:

  1. Announced — Added to discovery but not used for signing (PropagationTime duration)
  2. Active — Used for signing tokens (until RotationInterval is reached)
  3. Retired — No longer signs tokens, but remains in discovery for validation (RetentionDuration)
  4. Deleted — Removed from discovery (if DeleteRetiredKeys is true)

Multiple Signing Algorithms

Support multiple algorithms for different clients or APIs:

csharp
options.KeyManagement.SigningAlgorithms = new[]{    // RS256 for maximum compatibility (first = default)    new SigningAlgorithmOptions(SecurityAlgorithms.RsaSha256)    {        UseX509Certificate = true // Wrap in X.509 certificate    },    // PS256 for enhanced security    new SigningAlgorithmOptions(SecurityAlgorithms.RsaSsaPssSha256),    // ES256 for compact tokens    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:

csharp
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:

csharp
var idsvrBuilder = builder.Services.AddIdentityServer(options =>{    options.KeyManagement.Enabled = false;});
// Load key from secure storagevar signingKey = LoadKeyFromVault(); // Your key loading logicidsvrBuilder.AddSigningCredential(signingKey, SecurityAlgorithms.RsaSha256);

Manual Key Rotation (Three-Phase Process)

Rotating static keys requires careful sequencing to avoid breaking token validation:

csharp
// Phase 1: Announce new key (keep signing with old key)idsvrBuilder.AddSigningCredential(oldKey, SecurityAlgorithms.RsaSha256);idsvrBuilder.AddValidationKey(newKey, SecurityAlgorithms.RsaSha256);// Wait for all clients/APIs to refresh their JWKS cache (default: 24h)
// Phase 2: Start signing with new key (keep old key for validation)idsvrBuilder.AddSigningCredential(newKey, SecurityAlgorithms.RsaSha256);idsvrBuilder.AddValidationKey(oldKey, SecurityAlgorithms.RsaSha256);// Wait for all tokens signed with old key to expire
// Phase 3: Remove old keyidsvrBuilder.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.

csharp
var idsvrBuilder = builder.Services.AddIdentityServer(options =>{    // Remove expired sessions automatically    options.ServerSideSessions.RemoveExpiredSessionsFrequency = TimeSpan.FromMinutes(10);    options.ServerSideSessions.ExpiredSessionsTriggerBackchannelLogout = true;
    // Coordinate client token lifetimes with user sessions    options.Authentication.CoordinateClientLifetimesWithUserSession = true;})    // Server-side sessions are enabled by calling AddServerSideSessions()    .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

OptionDefaultPurpose
ServerSideSessions.RemoveExpiredSessionsFrequency10 minCleanup interval
ServerSideSessions.RemoveExpiredSessionstrueEnable automatic cleanup
ServerSideSessions.ExpiredSessionsTriggerBackchannelLogouttrueNotify 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

csharp
options.Events.RaiseErrorEvents = true;options.Events.RaiseInformationEvents = true;options.Events.RaiseFailureEvents = true;options.Events.RaiseSuccessEvents = true;

Authentication Cookie Settings

csharp
options.Authentication.CookieLifetime = TimeSpan.FromHours(10);options.Authentication.CookieSlidingExpiration = false;

Caching (with Store Caching Enabled)

csharp
options.Caching.ClientStoreExpiration = TimeSpan.FromMinutes(15);options.Caching.ResourceStoreExpiration = TimeSpan.FromMinutes(15);

Pushed Authorization Requests (PAR)

csharp
options.PushedAuthorization.Required = true; // Require all clients to use PAR

DPoP (Demonstrating Proof-of-Possession)

csharp
options.DPoP.ValidationMode = DPoPTokenExpirationValidationMode.Nonce;options.DPoP.ServerClockSkew = TimeSpan.FromMinutes(5);

Common Pitfalls

1. Missing openid Scope

csharp
// ❌ WRONG — OpenID Connect requires the openid scopenew Client{    AllowedScopes = { "profile", "api1" }}
// ✅ CORRECT — Always include openid for interactive clientsnew Client{    AllowedScopes =    {        IdentityServerConstants.StandardScopes.OpenId,        IdentityServerConstants.StandardScopes.Profile,        "api1"    }}

2. Mismatched Redirect URIs

csharp
// ❌ WRONG — Trailing slash mismatch causes "invalid_redirect_uri" errorRedirectUris = { "https://app.example.com/signin-oidc/" }// Client sends:   https://app.example.com/signin-oidc (no trailing slash)
// ✅ CORRECT — Exact match requiredRedirectUris = { "https://app.example.com/signin-oidc" }

3. Using ApiScope When ApiResource Is Needed

csharp
// ❌ WRONG — No audience claim, tokens work at any APIpublic static IEnumerable<ApiScope> ApiScopes =>    new[] { new ApiScope("read"), new ApiScope("write") };
// ✅ CORRECT — API resource sets audience for token isolationpublic static IEnumerable<ApiResource> ApiResources =>    new[]    {        new ApiResource("my-api")        {            Scopes = { "read", "write" }        }    };

4. Plaintext Client Secrets in Source Control

csharp
// ❌ WRONG — Secret in source codeClientSecrets = { new Secret("my-production-secret".Sha256()) }
// ✅ CORRECT — Load from configuration or vaultClientSecrets = { new Secret(configuration["Clients:Web:Secret"].Sha256()) }
// ✅ ALSO CORRECT — Use asymmetric credentials (no shared secret)// Client authenticates with a signed JWT assertion

5. Forgetting AllowOfflineAccess for Refresh Tokens

csharp
// ❌ Client requests "offline_access" scope but server doesn't allow itvar client = new Client{    AllowedGrantTypes = GrantTypes.Code,    AllowOfflineAccess = false, // Default    AllowedScopes = { "openid", "api1" }};// Client silently won't receive a refresh token
// ✅ Enable offline access explicitlyvar client = new Client{    AllowedGrantTypes = GrantTypes.Code,    AllowOfflineAccess = true,    AllowedScopes = { "openid", "api1" }};

6. Not Setting IssuerUri Behind a Reverse Proxy

csharp
// ❌ IdentityServer behind Nginx but IssuerUri defaults to internal hostname// Tokens contain iss: "http://internal-host:5000" — clients reject them
// ✅ Set IssuerUri to the external URLoptions.IssuerUri = "https://identity.example.com";

Production Configuration Checklist

SettingDevProduction
KeyManagement.Enabledtruetrue
KeyManagement.DataProtectKeystruetrue + configure Data Protection
Events.Raise*EventsOptionalAll true
ServerSideSessions (AddServerSideSessions())OptionalRecommended
SecretsIn-memory / configKey vault / certificates
StoreIn-memoryEF Core or custom
HTTPSOptionalRequired

Resources

来源与署名

来源:DuendeSoftware/duende-skills位于skills/identityserver-configuration提交fb32edc

许可证: 无许可证

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架