Claude
Skills
Sign in
Back

identityserver-hosting-setup

Included with Lifetime
$97 forever

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.

Cloud & DevOps

What this skill does


# 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 Pattern                 | Pros                                                                 | Cons                                        |
| ------------------------------- | -------------------------------------------------------------------- | ------------------------------------------- |
| **Separate host (recommended)** | Minimal attack surface, independent scaling, clear security boundary | Additional deployment artifact              |
| **Shared with web app**         | Fewer projects                                                       | Larger attack surface, coupled deployments  |
| **Shared with API**             | Fewer projects                                                       | Security risk, conflicting middleware needs |

## Step 1: Install Templates and Create a Project

```bash
dotnet new install Duende.Templates
dotnet 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.cs
var 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.cs
var 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.cs
builder.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.cs
var app = builder.Build();
app.UseStaticFiles();

app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();

app.MapDefaultControllerRoute();
```

### Pipeline Ordering Rules

| Order | Middleware                    | Notes                                              |
| ----- | ----------------------------- | -------------------------------------------------- |
| 1     | `UseStaticFiles()`            | Before IdentityServer                              |
| 2     | `UseRouting()`                | Before IdentityServer                              |
| 3     | `UseIdentityServer()`         | Includes `UseAuthentication()` internally          |
| 4     | `UseAuthorization()`          | Required after IdentityServer, must not be omitted |
| 5     | `MapDefaultControllerRoute()` | UI framework endpoints                             |

### Common Pipeline Anti-Patterns

```csharp
// ❌ WRONG: UseAuthentication is redundant (UseIdentityServer includes it)
app.UseAuthentication();
app.UseIdentityServer();

// ✅ CORRECT: UseIdentityServer already calls UseAuthentication
app.UseIdentityServer();
app.UseAuthorization();
```

```csharp
// ❌ WRONG: Missing UseAuthorization - required for the Duende UI template
app.UseIdentityServer();
app.MapDefaultControllerRoute();

// ✅ CORRECT: Always include UseAuthorization after UseIdentityServer
app.UseIdentityServer();
app.UseAuthorization();
app.MapDefaultControllerRoute();
```

```csharp
// ❌ WRONG: IdentityServer before routing
app.UseIdentityServer();
app.UseRouting();

// ✅ CORRECT: Routing before IdentityServer
app.UseRouting();
app.UseIdentityServer();
```

## Step 4: Configure Essential IdentityServerOptions

```csharp
// Program.cs
var 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

| Property                                    | Default           | Purpose                                           |
| ------------------------------------------- | ----------------- | ------------------------------------------------- |
| `IssuerUri`                                 | inferred from URL | Token issuer name in discovery and tokens         |
| `LowerCaseIssuerUri`                        | `true`            | Lowercase inferred issuer URIs                    |
| `AccessTokenJwtType`                        | `"at+jwt"`        | `typ` header in JWT access tokens (RFC 9068)      |
| `EmitScopesAsSpaceDelimitedStringInJwt`     | `false`           | Scope claim format in JWTs                        |
| `EmitStaticAudienceClaim`                   | `false`           | Static `aud` claim in `{issuer}/resources` format |
| `EmitIssuerIdentificationResponseParameter` | `true`            | `iss` 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 sto

Related in Cloud & DevOps