Identityserver Hosting Setup

by DuendeSoftwarefb32edc51982No license9 starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated 4 weeks ago

Setting up and hosting Duende IdentityServer in ASP.NET Core applications, including DI registration, middleware pipeline, hosting patterns, essential options, license configuration, and ASP.NET Identity integration.

AI-generated overview

Guides setup and hosting of Duende IdentityServer in ASP.NET Core, covering DI, middleware, options, licensing and Identity integration.

What it does
This skill provides step-by-step instructions for setting up and hosting Duende IdentityServer in ASP.NET Core applications. It covers service registration in dependency injection, request pipeline ordering, essential IdentityServerOptions, license key configuration, ASP.NET Identity integration, and production concerns such as forwarded headers and data protection. It produces configuration guidance and code snippets rather than runnable scripts.
When to use it
Use it when starting a new Duende IdentityServer project, wiring its middleware and DI, choosing between separate and shared hosting, or integrating ASP.NET Identity for user management. It is also relevant when configuring production hosting behind proxies or load balancers.
Requirements
Requires an ASP.NET Core project and the Duende IdentityServer NuGet packages, including Duende.Templates and optionally Duende.IdentityServer.AspNetIdentity. A valid Duende license key is needed for production use. Instructions only; no scripts are included.

Setting Up and Hosting IdentityServer

When to Use This Skill

  • Setting up a new Duende IdentityServer project from scratch
  • Configuring the ASP.NET Core DI system and middleware pipeline for IdentityServer
  • Deciding between separate vs shared hosting patterns
  • Integrating IdentityServer with ASP.NET Identity for user management
  • Configuring IdentityServerOptions (issuer, key management, endpoints)
  • Setting up proxy/load balancer forwarded headers
  • Configuring data protection for production deployments
  • Understanding the IdentityServer middleware pipeline ordering

Docs: https://docs.duendesoftware.com/identityserver/fundamentals

Core Concepts

Duende IdentityServer is middleware that adds OpenID Connect and OAuth 2.0 endpoints to an ASP.NET Core host. It requires two setup steps: registering services in DI and adding middleware to the request pipeline.

Architecture Decision: Separate vs Shared Host

IdentityServer should be in its own dedicated application to minimize the attack surface. While it is technically possible to co-host IdentityServer with clients or APIs, this is not recommended.

Hosting PatternProsCons
Separate host (recommended)Minimal attack surface, independent scaling, clear security boundaryAdditional deployment artifact
Shared with web appFewer projectsLarger attack surface, coupled deployments
Shared with APIFewer projectsSecurity risk, conflicting middleware needs

Step 1: Install Templates and Create a Project

bash
dotnet new install Duende.Templatesdotnet new duende-is-empty -n IdentityServer

The duende-is-empty template creates a minimal project with the IdentityServer NuGet package installed and basic configuration.

Step 2: Register IdentityServer Services (DI)

Call AddIdentityServer on the service collection to register all necessary services. This method also calls AddAuthentication internally.

csharp
// Program.csvar idsvrBuilder = builder.Services.AddIdentityServer(options =>{    // Configure IdentityServerOptions here});

Adding Configuration Stores

The builder object returned by AddIdentityServer provides extension methods to add configuration stores for clients, resources, and scopes:

csharp
// Program.csvar idsvrBuilder = builder.Services.AddIdentityServer()    .AddInMemoryClients(Config.Clients)    .AddInMemoryIdentityResources(Config.IdentityResources)    .AddInMemoryApiScopes(Config.ApiScopes);

Store options:

  • In-memory stores - good for development, demos, and static configuration
  • EntityFramework stores - production-ready, supports dynamic configuration
  • Custom stores - implement the store interfaces for any backing store

Minimal Working Example

csharp
// Program.csbuilder.Services.AddIdentityServer()    .AddInMemoryApiScopes(Config.ApiScopes)    .AddInMemoryClients(Config.Clients);
var app = builder.Build();
app.UseStaticFiles();app.UseRouting();app.UseIdentityServer();app.UseAuthorization();app.MapDefaultControllerRoute();
app.Run();

Step 3: Configure the Request Pipeline

Add UseIdentityServer middleware to the pipeline. Pipeline ordering is critical.

csharp
// Program.csvar app = builder.Build();app.UseStaticFiles();
app.UseRouting();app.UseIdentityServer();app.UseAuthorization();
app.MapDefaultControllerRoute();

Pipeline Ordering Rules

OrderMiddlewareNotes
1UseStaticFiles()Before IdentityServer
2UseRouting()Before IdentityServer
3UseIdentityServer()Includes UseAuthentication() internally
4UseAuthorization()Required after IdentityServer, must not be omitted
5MapDefaultControllerRoute()UI framework endpoints

Common Pipeline Anti-Patterns

csharp
// ❌ WRONG: UseAuthentication is redundant (UseIdentityServer includes it)app.UseAuthentication();app.UseIdentityServer();
// ✅ CORRECT: UseIdentityServer already calls UseAuthenticationapp.UseIdentityServer();app.UseAuthorization();
csharp
// ❌ WRONG: Missing UseAuthorization - required for the Duende UI templateapp.UseIdentityServer();app.MapDefaultControllerRoute();
// ✅ CORRECT: Always include UseAuthorization after UseIdentityServerapp.UseIdentityServer();app.UseAuthorization();app.MapDefaultControllerRoute();
csharp
// ❌ WRONG: IdentityServer before routingapp.UseIdentityServer();app.UseRouting();
// ✅ CORRECT: Routing before IdentityServerapp.UseRouting();app.UseIdentityServer();

Step 4: Configure Essential IdentityServerOptions

csharp
// Program.csvar idsvrBuilder = builder.Services.AddIdentityServer(options =>{    // IssuerUri: Not recommended to set; inferred from request URL by default.    // Set only when IdentityServer is accessed on a different address than the    // expected issuer (e.g., internal Kubernetes address).    // options.IssuerUri = "https://identity.example.com";
    // Emit scopes as space-delimited string per RFC 9068    options.EmitScopesAsSpaceDelimitedStringInJwt = false; // default, array format
    // Emit static audience claim in format {issuer}/resources    options.EmitStaticAudienceClaim = false; // default
    // Emit iss response parameter on authorize responses (RFC 9207)    options.EmitIssuerIdentificationResponseParameter = true; // default});

Key Configuration Properties

PropertyDefaultPurpose
IssuerUriinferred from URLToken issuer name in discovery and tokens
LowerCaseIssuerUritrueLowercase inferred issuer URIs
AccessTokenJwtType"at+jwt"typ header in JWT access tokens (RFC 9068)
EmitScopesAsSpaceDelimitedStringInJwtfalseScope claim format in JWTs
EmitStaticAudienceClaimfalseStatic aud claim in {issuer}/resources format
EmitIssuerIdentificationResponseParametertrueiss param on authorize responses (RFC 9207)

Step 5: Configure the License Key

Duende IdentityServer requires a valid license for production use. Without a license key, IdentityServer runs in trial/community mode and will log a warning on startup.

Set the license key via options.LicenseKey or via configuration:

csharp
// Option 1: Inline in AddIdentityServer (not recommended for production — keep out of source control)builder.Services.AddIdentityServer(options =>{    options.LicenseKey = "YOUR_LICENSE_KEY";});
// Option 2: From configuration (recommended)builder.Services.AddIdentityServer(options =>{    options.LicenseKey = builder.Configuration["IdentityServer:LicenseKey"];});

Store the key in a secret manager, environment variable, or key vault — never in source-controlled appsettings.json.

Step 6: ASP.NET Identity Integration

To use ASP.NET Identity as the user store for IdentityServer, install the integration package and configure both systems:

bash
dotnet add package Duende.IdentityServer.AspNetIdentity
csharp
// Program.csbuilder.Services.AddIdentity<ApplicationUser, IdentityRole>()    .AddEntityFrameworkStores<ApplicationDbContext>()    .AddDefaultTokenProviders();
builder.Services.AddIdentityServer()    .AddAspNetIdentity<ApplicationUser>();

What AddAspNetIdentity Configures

AddAspNetIdentity<TUser> registers the following IdentityServer implementations:

  • IProfileService - uses IUserClaimsPrincipalFactory to add claims to tokens
  • IResourceOwnerPasswordValidator - supports the password grant type
  • IUserClaimsPrincipalFactory - a wrapper implementation that calls through to the previously registered factory and adds extra IdentityServer-specific claims

Custom IUserClaimsPrincipalFactory

If you register a custom IUserClaimsPrincipalFactory before calling AddAspNetIdentity, the IdentityServer registration will resolve your factory and call through to it, layering additional claims on top:

csharp
// Program.cs
// Register custom factory BEFORE AddAspNetIdentitybuilder.Services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, CustomClaimsPrincipalFactory>();
builder.Services.AddIdentityServer()    .AddAspNetIdentity<ApplicationUser>();

Inactive User Handling

ASP.NET Identity has no built-in concept of inactive users. The default IsActiveAsync implementation returns true. To support enable/disable functionality:

csharp
public class CustomProfileService : ProfileService<ApplicationUser>{    public CustomProfileService(        UserManager<ApplicationUser> userManager,        IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory)        : base(userManager, claimsFactory)    { }
    protected override Task<bool> IsUserActiveAsync(ApplicationUser user)    {        return Task.FromResult(user.IsEnabled); // your custom property    }}

Template Alternative

Use the duende-is-aspid template for a pre-configured ASP.NET Identity integration:

bash
dotnet new duende-is-aspid -n IdentityServer

Production Deployment: Proxy and Load Balancer Configuration

When behind a reverse proxy or load balancer, the proxy obscures request scheme and IP address. This causes common symptoms:

  • HTTPS downgraded to HTTP in discovery document
  • Incorrect host names in discovery or redirects
  • Cookies missing the secure attribute

Solution: Forwarded Headers Middleware

Option 1: Environment variable (simple) Set ASPNETCORE_FORWARDEDHEADERS_ENABLED=true for cloud/Kubernetes environments.

Option 2: Explicit configuration (production)

csharp
// Program.csbuilder.Services.Configure<ForwardedHeadersOptions>(options =>{    options.ForwardedHeaders = ForwardedHeaders.XForwardedHost |                                ForwardedHeaders.XForwardedProto;
    options.KnownProxies.Add(IPAddress.Parse("203.0.113.42"));    options.ForwardLimit = 1;});

Add UseForwardedHeaders() early in the pipeline, before UseIdentityServer().

Production Deployment: Data Protection

Data protection is critical for IdentityServer. It protects signing keys at rest, persisted grants, server-side sessions, and authentication cookies. See ASP.NET Core Data Protection for comprehensive guidance covering all Duende SDKs.

csharp
// Program.csbuilder.Services.AddDataProtection()    .PersistKeysToFoo()       // Choose persistence (FileSystem, DbContext, Azure, Redis, etc.)    .ProtectKeysWithBar()     // Choose key protection (Certificate, Azure Key Vault, etc.)    .SetApplicationName("My.IdentityServer"); // Prevent key isolation issues

Data Protection Checklist

RequirementWhy
Persist keys to durable storageKeys are lost on restart without persistence
Share keys across load-balanced instancesEach instance must read data protected by other instances
Set explicit application namePrevents key isolation across deployments
Ensure storage durabilityRedis without persistence or ephemeral filesystems lose keys

Data Protection Keys vs Signing Keys

These are completely separate:

Data Protection KeysIdentityServer Signing Keys
PurposeEncrypt/sign sensitive data (cookies, grants)Sign tokens (JWT, id_token)
CryptographySymmetric (private key)Asymmetric (public/private key pair)
FrameworkASP.NET Core Data ProtectionIdentityServer Key Management
PublicNoPublic keys published in discovery

Common Pitfalls

  1. Missing UseAuthorization() - The Duende UI template requires authorization middleware. Omitting it causes authorization failures in the UI pages.

  2. Redundant UseAuthentication() - UseIdentityServer() already includes UseAuthentication(). Adding both is unnecessary but not harmful.

  3. Data protection not configured for production - The default file-based key storage does not survive container restarts or work across load-balanced instances. Always configure persistent, shared key storage.

  4. Issuer mismatch - If IssuerUri is set manually, clients must know this exact value. Prefer letting IdentityServer infer the issuer from request URLs.

  5. Keys directory in source control - The ~/keys directory created by automatic key management contains cryptographic secrets and must be excluded from source control via .gitignore.

  6. Shared hosting with APIs/clients - Co-hosting IdentityServer with other applications increases the attack surface. Use a dedicated host.

  7. Not calling AddAspNetIdentity after AddIdentity - When using ASP.NET Identity, you must call both. AddIdentity configures ASP.NET Identity; AddAspNetIdentity bridges it to IdentityServer.


Related Skills

  • identityserver-configuration — client definitions, resources, scopes
  • identityserver-deployment — production deployment, data protection, health checks
  • identityserver-aspire — orchestrating IdentityServer in Aspire AppHost

Source and attribution

Source:DuendeSoftware/duende-skillsinskills/identityserver-hosting-setupat commitfb32edc

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal