← Duende SkillsCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Duende Skills
Snapshot Sep 30, 2026 · 23:14 UTC · version 0.3.0
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"name": "identityserver4-migration",
"description": "Migrating from IdentityServer4 to Duende IdentityServer v8. Covers NuGet package replacement, namespace changes, API surface changes, EF Core database schema migrations, .NET target framework upgrade, license configuration, signing key migration, data protection, and UI template updates.",
"included_files": [],
"skill_md_contents": "---\nname: identityserver4-migration\ndescription: Migrating from IdentityServer4 to Duende IdentityServer v8. Covers NuGet package replacement, namespace changes, API surface changes, EF Core database schema migrations, .NET target framework upgrade, license configuration, signing key migration, data protection, and UI template updates.\ninvocable: false\n---\n\n# Migrating from IdentityServer4 to Duende IdentityServer\n\n> **Scope: IdentityServer4 only.** This skill covers migrating from **IdentityServer4** (v3.x and v4.x) to Duende IdentityServer. It does **not** cover migrating from **IdentityServer3** (the older Thinktecture/`IdentityServer3` NuGet package that ran on OWIN/Katana and .NET Framework). IdentityServer3 is a fundamentally different product with a different API surface, configuration model, and hosting stack. If you are on IdentityServer3, you must first port to IdentityServer4 on ASP.NET Core before using this guide.\n\n## When to Use This Skill\n\n- Planning a migration from IdentityServer4 to Duende IdentityServer — running the migration analysis tool\n- Upgrading a project from IdentityServer4 (v3.x or v4.x) to Duende IdentityServer v8\n- Replacing IdentityServer4 NuGet packages with Duende equivalents\n- Updating `IdentityServer4.*` namespaces to `Duende.IdentityServer.*`\n- Migrating EF Core database schemas from IdentityServer4 to Duende IdentityServer\n- Upgrading the .NET target framework from `netcoreapp3.1` or `net5.0` to a current LTS version\n- Resolving breaking API changes between IdentityServer4 and Duende IdentityServer\n- Converting `Startup.cs`/`Program.cs` hosting patterns from Generic Host to minimal hosting\n- Configuring the Duende IdentityServer license key after migration\n- Determining the right Duende license edition based on client inventory (interactive vs. non-interactive)\n- Preserving the issuer URI to maintain token and client trust continuity\n- Migrating signing keys from IdentityServer4 developer signing credential to Duende automatic key management\n- Verifying third-party authentication scheme compatibility with the new .NET version\n- Updating UI templates (login, logout, consent) from IdentityServer4 Quickstart UI to Duende templates\n\n## Core Principles\n\n**Migration has two stages if starting from v3.x.** IdentityServer4 v3 → v4 introduced breaking changes in the `ApiResource`/`ApiScope` relationship (parent-child to many-to-many). If you are on v3, first migrate to v4 semantics, then migrate from v4 to Duende. If you are already on IdentityServer4 v4.x, you can go directly to Duende.\n\n**Duende IdentityServer is the direct successor to IdentityServer4.** The API surface is intentionally similar — most code changes are namespace and package renames. Behavioral changes are minimal, but the database schema has new tables and columns for features like automatic key management, server-side sessions, DPoP, and PAR.\n\n**The .NET target framework must be upgraded alongside the IdentityServer migration.** IdentityServer4 ran on `netcoreapp3.1` or `net5.0`. Duende IdentityServer v8 requires `net10.0`. You must follow Microsoft's ASP.NET Core migration guides for each major version jump.\n\n**Database migrations require careful handling to avoid data loss.** The v3 → v4 schema change renames tables and restructures relationships. A naive EF Core migration will drop and recreate tables, losing data. Use the provided delta SQL scripts or manually craft migrations that preserve data.\n\n**Licensing is required for production use.** Duende IdentityServer requires a valid license key for production. Without one, it runs in community/trial mode and logs a warning on startup.\n\nDocs: https://docs.duendesoftware.com/identityserver/upgrades\n\n---\n\n## Migration Path Overview\n\n```\nIdentityServer4 v3.x or v4.x\n │\n ▼ (Step 0: Run Migration Analysis Tool against running instance)\n │\n ▼ (Stage 1: v3 → v4 API changes + DB migration — skip if already on v4)\nIdentityServer4 v4.x\n │\n ▼ (Stage 2: packages + namespaces + .NET upgrade + DB migration)\nDuende IdentityServer v8.x\n```\n\nIf already on IdentityServer4 v4.x, skip directly to Stage 2.\n\n---\n\n## Step 0: Run the Migration Analysis Tool (Recommended)\n\nBefore making any code changes, run the **Migration Analysis Tool** against your running IdentityServer4 instance. This tool inspects your live configuration and produces a report with specific recommendations.\n\nThe tool is a single file, [`MigrationAnalysisController.cs`](https://docs.duendesoftware.com/identityserver/upgrades/identityserver4-upgrade-analysis/), that you drop into your existing IdentityServer4 project. It does not require additional NuGet packages.\n\n### What the tool inspects\n\n| Data Point | Why It Matters |\n|------------|---------------|\n| **.NET runtime version** | Flags if you need to upgrade to .NET 10 |\n| **IdentityServer4 version** | Determines if Stage 1 (v3 → v4) is needed before proceeding |\n| **Client inventory** | Counts interactive (authorization code) vs. non-interactive (client credentials) clients — this determines which [Duende license edition](https://duendesoftware.com/products/identityserver) you need |\n| **Issuer URI** | Reports the configured `IssuerUri` — must be preserved in Duende to avoid breaking existing tokens and client trust relationships |\n| **Signing credential store type** | Identifies custom signing stores that may need compatibility updates |\n| **Signing credential key ID** | Records the current key ID for signing key migration planning |\n| **Data protection application name** | Flags missing or path-based discriminators that will break after .NET upgrade |\n| **Data protection repository type** | Warns if keys are stored ephemerally (lost on restart) instead of a persistent store |\n| **Authentication schemes** | Lists all registered authentication handlers — third-party handlers (non-Microsoft, non-IdentityServer4) may need version updates for the new ASP.NET Core version |\n\n### Usage\n\n1. Download `MigrationAnalysisController.cs` and add it to your IdentityServer4 project\n2. **Update the authorization check** in the `Index()` method — the default placeholder checks for username `\"scott\"` which you must replace with your own authorization logic\n3. Build, run, and navigate to `/MigrationAnalysis` while authenticated\n4. Review the report and use it to plan your migration\n\nThe tool loads clients from in-memory configuration or EF Core stores automatically. If you use a custom client store, you will need to modify the constructor to wire up your client retrieval.\n\n> **Note:** Duende also offers a [free IdentityServer4 upgrade assessment](https://duendesoftware.com) to walk through your upgrade path.\n\n---\n\n## Stage 1: IdentityServer4 v3.x → v4.x\n\nSkip this section if you are already on IdentityServer4 v4.x.\n\n### Step 1.1: Update NuGet Packages to v4\n\n```xml\n<!-- Old (v3) -->\n<PackageReference Include=\"IdentityServer4\" Version=\"3.1.4\" />\n<PackageReference Include=\"IdentityServer4.EntityFramework\" Version=\"3.1.4\" />\n<PackageReference Include=\"IdentityServer4.AspNetIdentity\" Version=\"3.1.4\" />\n\n<!-- New (v4) -->\n<PackageReference Include=\"IdentityServer4\" Version=\"4.1.2\" />\n<PackageReference Include=\"IdentityServer4.EntityFramework\" Version=\"4.1.2\" />\n<PackageReference Include=\"IdentityServer4.AspNetIdentity\" Version=\"4.1.2\" />\n```\n\n### Step 1.2: Register API Scopes Separately\n\nIn v3, `ApiScope` was a child of `ApiResource`. In v4, scopes are independent top-level objects with a many-to-many relationship to API resources. You must register them separately:\n\n```csharp\n// v3: Scopes nested inside ApiResource\nnew ApiResource(\"api1\", \"My API\")\n{\n Scopes = { new Scope(\"api1.read\"), new Scope(\"api1.write\") }\n}\n\n// v4: Scopes are independent; ApiResource references scope names\npublic static IEnumerable<ApiScope> ApiScopes => new[]\n{\n new ApiScope(\"api1.read\", \"Read access to API 1\"),\n new ApiScope(\"api1.write\", \"Write access to API 1\")\n};\n\npublic static IEnumerable<ApiResource> ApiResources => new[]\n{\n new ApiResource(\"api1\", \"My API\")\n {\n Scopes = { \"api1.read\", \"api1.write\" } // string references, not Scope objects\n }\n};\n\n// Register both:\nservices.AddIdentityServer()\n .AddInMemoryApiScopes(Config.ApiScopes) // NEW in v4\n .AddInMemoryApiResources(Config.ApiResources)\n .AddInMemoryClients(Config.Clients);\n```\n\n### Step 1.3: Fix Breaking API Changes (v3 → v4)\n\n**HttpContext.SignInAsync signature change:**\n\n```csharp\n// v3\nawait HttpContext.SignInAsync(user.SubjectId, user.Username, props);\n\n// v4\nvar isuser = new IdentityServerUser(user.SubjectId)\n{\n DisplayName = user.Username\n};\nawait HttpContext.SignInAsync(isuser, props);\n```\n\n**AuthorizationRequest property changes:**\n\n```csharp\n// v3\nvar clientId = request.ClientId;\nvar scopes = request.ScopesRequested;\nvar isPkce = await _clientStore.IsPkceClientAsync(context.ClientId);\n\n// v4\nvar clientId = request.Client.ClientId;\nvar scopes = request.ValidatedResources.RawScopeValues;\nvar isPkce = context.IsNativeClient();\n```\n\n**Consent response changes:**\n\n```csharp\n// v3\nvar grantedConsent = new ConsentResponse\n{\n ScopesConsented = consentedScopes\n};\n\n// v4\nvar grantedConsent = new ConsentResponse\n{\n ScopesValuesConsented = consentedScopes // renamed property\n};\n```\n\n**Grant management method renames:**\n\n```csharp\n// v3\nawait _interaction.GetAllUserConsentsAsync();\n\n// v4\nawait _interaction.GetAllUserGrantsAsync();\n```\n\n**External provider callback consolidation:**\n\n```csharp\n// v3: separate methods per protocol\nProcessLoginCallbackForOidc();\nProcessLoginCallbackForWsFed();\nProcessLoginCallbackForSaml2p();\n\n// v4: single unified method\nProcessLoginCallback();\n```\n\n### Step 1.4: Migrate the Database (v3 → v4)\n\n**PersistedGrantDbContext** — Standard EF migration:\n\n```bash\ndotnet ef migrations add Grants_v4 -c PersistedGrantDbContext -o Migrations/PersistedGrantDb\ndotnet ef database update -c PersistedGrantDbContext\n```\n\nNew columns added: `ConsumedTime`, `Description`, `SessionId` on `PersistedGrants` and `DeviceCodes`.\n\n**ConfigurationDbContext** — Requires custom SQL to preserve data:\n\nThe v3 → v4 schema change renames tables:\n- `ApiClaims` → `ApiResourceClaims`\n- `ApiProperties` → `ApiResourceProperties`\n- `ApiSecrets` → `ApiResourceSecrets`\n- `IdentityClaims` → `IdentityResourceClaims`\n- `IdentityProperties` → `IdentityResourceProperties`\n\nAnd restructures the `ApiScopes` relationship (scopes become independent, linked via `ApiResourceScopes` join table).\n\n**Do not rely on auto-generated EF migrations for this step — they will drop and recreate tables, losing data.** Instead:\n\n1. Create the migration scaffold:\n ```bash\n dotnet ef migrations add Config_v4 -c ConfigurationDbContext -o Migrations/ConfigurationDb\n ```\n\n2. Embed a custom delta SQL script that migrates data before dropping old tables:\n ```sql\n -- Move data from old tables to new tables\n INSERT INTO ApiResourceClaims (Id, [Type], ApiResourceId)\n SELECT Id, [Type], ApiResourceId FROM ApiClaims;\n\n INSERT INTO ApiResourceProperties (Id, [Key], [Value], ApiResourceId)\n SELECT Id, [Key], [Value], ApiResourceId FROM ApiProperties;\n\n INSERT INTO ApiResourceSecrets (Id, [Description], [Value], [Expiration], [Type], [Created], ApiResourceId)\n SELECT Id, [Description], [Value], [Expiration], [Type], [Created], ApiResourceId FROM ApiSecrets;\n\n INSERT INTO IdentityResourceClaims (Id, [Type], IdentityResourceId)\n SELECT Id, [Type], IdentityResourceId FROM IdentityClaims;\n\n INSERT INTO IdentityResourceProperties (Id, [Key], [Value], IdentityResourceId)\n SELECT Id, [Key], [Value], IdentityResourceId FROM IdentityProperties;\n\n -- Migrate scope-resource relationship to join table\n INSERT INTO ApiResourceScopes ([Scope], [ApiResourceId])\n SELECT [Name], [ApiResourceId] FROM ApiScopes;\n\n -- Remove old foreign key column from ApiScopes\n -- (handled by EF migration after data is moved)\n ```\n\n3. Modify the generated migration to execute the SQL script before the destructive operations.\n\n4. Apply: `dotnet ef database update -c ConfigurationDbContext`\n\nReference implementation: [UpgradeSample-IdentityServer4-v3](https://github.com/DuendeSoftware/UpgradeSample-IdentityServer4-v3)\n\n---\n\n## Stage 2: IdentityServer4 v4.x → Duende IdentityServer v8.x\n\n### Step 2.1: Update .NET Target Framework\n\nUpdate from `netcoreapp3.1` or `net5.0` to `net10.0` (required by Duende IdentityServer v8):\n\n```xml\n<!-- Old -->\n<TargetFramework>netcoreapp3.1</TargetFramework>\n\n<!-- New -->\n<TargetFramework>net10.0</TargetFramework>\n```\n\nFollow the Microsoft ASP.NET Core migration guides for each major version jump. Key changes include:\n- Minimal hosting model (`WebApplication.CreateBuilder` replaces `Startup.cs` + `Program.cs` pattern)\n- Nullable reference types enabled by default\n- `ImplicitUsings` enabled by default\n- Updated `Microsoft.EntityFrameworkCore.*` packages to match the .NET version\n\n### Step 2.2: Replace NuGet Packages\n\n```xml\n<!-- Old (IdentityServer4) -->\n<PackageReference Include=\"IdentityServer4\" Version=\"4.1.2\" />\n<PackageReference Include=\"IdentityServer4.EntityFramework\" Version=\"4.1.2\" />\n<PackageReference Include=\"IdentityServer4.AspNetIdentity\" Version=\"4.1.2\" />\n<PackageReference Include=\"IdentityModel\" Version=\"5.2.0\" />\n\n<!-- New (Duende) -->\n<PackageReference Include=\"Duende.IdentityServer\" Version=\"8.0.0\" />\n<PackageReference Include=\"Duende.IdentityServer.EntityFramework\" Version=\"8.0.0\" />\n<PackageReference Include=\"Duende.IdentityServer.AspNetIdentity\" Version=\"8.0.0\" />\n<PackageReference Include=\"Duende.IdentityModel\" Version=\"8.0.0\" />\n```\n\nAlso update EF Core and other ASP.NET Core packages to match the new target framework:\n\n```xml\n<PackageReference Include=\"Microsoft.EntityFrameworkCore.SqlServer\" Version=\"10.0.0\" />\n<PackageReference Include=\"Microsoft.EntityFrameworkCore.Design\" Version=\"10.0.0\" />\n```\n\n### Step 2.3: Update Namespaces\n\nSearch and replace all `IdentityServer4` namespaces with `Duende.IdentityServer`:\n\n```csharp\n// Old\nusing IdentityServer4;\nusing IdentityServer4.Models;\nusing IdentityServer4.Services;\nusing IdentityServer4.Stores;\nusing IdentityServer4.Extensions;\nusing IdentityServer4.Events;\nusing IdentityServer4.Test;\nusing IdentityServer4.Validation;\nusing IdentityServer4.EntityFramework.DbContexts;\nusing IdentityServer4.EntityFramework.Mappers;\nusing IdentityServer4.EntityFramework.Options;\nusing IdentityModel;\n\n// New\nusing Duende.IdentityServer;\nusing Duende.IdentityServer.Models;\nusing Duende.IdentityServer.Services;\nusing Duende.IdentityServer.Stores;\nusing Duende.IdentityServer.Extensions;\nusing Duende.IdentityServer.Events;\nusing Duende.IdentityServer.Test;\nusing Duende.IdentityServer.Validation;\nusing Duende.IdentityServer.EntityFramework.DbContexts;\nusing Duende.IdentityServer.EntityFramework.Mappers;\nusing Duende.IdentityServer.EntityFramework.Options;\nusing Duende.IdentityModel;\n```\n\nAlso update any fully-qualified type references in code and configuration files.\n\n### Step 2.4: Convert to Minimal Hosting (Recommended)\n\nIf migrating from `netcoreapp3.1`, convert the `Startup.cs` + `Program.cs` pattern to minimal hosting:\n\n```csharp\n// Old: Startup.cs + Program.cs pattern\npublic class Startup\n{\n public void ConfigureServices(IServiceCollection services)\n {\n services.AddIdentityServer()\n .AddConfigurationStore(options => { /* ... */ })\n .AddOperationalStore(options => { /* ... */ });\n }\n\n public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n {\n app.UseRouting();\n app.UseIdentityServer();\n app.UseAuthorization();\n app.UseEndpoints(e => e.MapDefaultControllerRoute());\n }\n}\n\n// New: Minimal hosting in Program.cs\nvar builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddIdentityServer()\n .AddConfigurationStore(options => { /* ... */ })\n .AddOperationalStore(options => { /* ... */ });\n\nvar app = builder.Build();\n\napp.UseRouting();\napp.UseIdentityServer();\napp.UseAuthorization();\napp.MapDefaultControllerRoute();\n\napp.Run();\n```\n\n### Step 2.5: Preserve the Issuer URI\n\nThe issuer URI (`iss` claim) must remain identical after migration. If it changes, all existing tokens become invalid and client trust relationships break.\n\n```csharp\n// If you had an explicit IssuerUri in IdentityServer4, keep it:\nbuilder.Services.AddIdentityServer(options =>\n{\n options.IssuerUri = \"https://identity.example.com\";\n});\n\n// If the issuer was inferred from the request URL in IS4 (no explicit IssuerUri set),\n// verify that the Duende host uses the same URL/port/scheme.\n```\n\nIf your IS4 instance inferred the issuer from the request (no explicit `IssuerUri` configured), check the `/.well-known/openid-configuration` of your old instance, note the `issuer` value, and explicitly set it in the Duende configuration to be safe.\n\n### Step 2.6: Configure the Duende License Key\n\nAdd the license key configuration — required for production:\n\n```csharp\nbuilder.Services.AddIdentityServer(options =>\n{\n options.LicenseKey = builder.Configuration[\"IdentityServer:LicenseKey\"];\n});\n```\n\nStore the license key in a secret manager, environment variable, or key vault — never in source-controlled `appsettings.json`.\n\nWithout a license key, IdentityServer runs in community/trial mode and logs a warning on startup. This is acceptable for local development.\n\n**Choosing the right edition:** The license edition depends on your client inventory. Count interactive clients (those using `authorization_code` grant type — typically web apps, SPAs, native apps) vs. non-interactive clients (those using `client_credentials` — typically machine-to-machine). Run the Migration Analysis Tool (Step 0) to get these counts automatically. See [Duende IdentityServer Pricing](https://duendesoftware.com/products/identityserver) for edition thresholds.\n\n### Step 2.7: Remove AddDeveloperSigningCredential\n\nIdentityServer4 projects commonly used `AddDeveloperSigningCredential()` for development signing keys. Duende IdentityServer includes automatic key management (Business/Enterprise editions):\n\n```csharp\n// Old (remove)\nservices.AddIdentityServer()\n .AddDeveloperSigningCredential();\n\n// New: Automatic key management is built-in (Business/Enterprise)\n// No explicit call needed — keys are created and rotated automatically\n\n// Or for Community edition, configure a static signing credential:\nbuilder.Services.AddIdentityServer()\n .AddSigningCredential(new X509Certificate2(\"signing.pfx\", \"password\"));\n```\n\n### Step 2.8: Migrate the Database Schema (v4 → Duende v8)\n\nCreate EF Core migrations for both contexts:\n\n```bash\ndotnet ef migrations add UpdateToDuende_v8 -c PersistedGrantDbContext \\\n -o Data/Migrations/IdentityServer/PersistedGrantDb\n\ndotnet ef migrations add UpdateToDuende_v8 -c ConfigurationDbContext \\\n -o Data/Migrations/IdentityServer/ConfigurationDb\n```\n\nApply:\n\n```bash\ndotnet ef database update -c PersistedGrantDbContext\ndotnet ef database update -c ConfigurationDbContext\n```\n\n**New tables and columns in Duende IdentityServer v8:**\n\n| Context | Change | Purpose |\n|---------|--------|---------|\n| Operational | `Keys` table (new) | Automatic key management storage |\n| Operational | `ServerSideSessions` table (new) | Server-side session management |\n| Operational | `PushedAuthorizationRequests` table (new) | PAR support |\n| Operational | `SamlSignInStates` table (new) | SAML SSO state |\n| Operational | `SamlLogoutSessions` table (new) | SAML SLO session tracking |\n| Operational | `ConsumedTime` index on `PersistedGrants` | Performance optimization |\n| Configuration | `IdentityProviders` table (new) | Dynamic OIDC provider configuration |\n| Configuration | `SamlServiceProviders` table (new) | SAML SP registration |\n| Configuration | `RequireResourceIndicator` column on `ApiResources` | Resource indicator support |\n| Configuration | Timestamp columns on entities | Created, updated, last accessed tracking |\n| Configuration | Unique constraints on child tables | Prevent duplicate entries |\n| Client | `InitiateLoginUri` | Third-party initiated login |\n| Client | `RequireDPoP`, `DPoPValidationMode`, `DPoPClockSkew` | DPoP enforcement |\n| Client | `RequirePushedAuthorization`, `PushedAuthorizationLifetime` | PAR requirement |\n\n**Note on redirect URI column length:** The `RedirectUri` column length was reduced from 2000 to 400 characters. This is safe unless you use redirect URIs longer than 400 characters, which is extremely uncommon.\n\n### Step 2.9: Configure Data Protection\n\nSet an explicit application name to prevent data protection key invalidation when paths change between .NET versions. See [ASP.NET Core Data Protection](https://docs.duendesoftware.com/general/data-protection/) for comprehensive guidance — this is a cross-cutting concern for all Duende SDKs.\n\n```csharp\nbuilder.Services.AddDataProtection()\n .PersistKeysToDbContext<DataProtectionKeyContext>()\n .SetApplicationName(\"YourIdentityServer\");\n```\n\n**Why this matters:** The default application name (content root path) changed between .NET versions:\n- .NET 3.1–5: content root without trailing separator\n- .NET 6: content root with trailing separator (breaking change)\n- .NET 7+: content root without trailing separator\n\nIf you relied on the default, tokens encrypted before the .NET upgrade will not decrypt after it.\n\n**Persistent key storage is required in production.** If data protection has no explicit repository configured (`PersistKeysToDbContext`, `PersistKeysToFileSystem`, `PersistKeysToAzureBlobStorage`, etc.), keys are stored in-memory and lost on restart — meaning all encrypted data (persisted grants, cookies, antiforgery tokens) becomes unreadable. The Migration Analysis Tool (Step 0) flags this as `(not set)` in the data protection repository type check. If you see this, add persistent key storage before migrating.\n\n### Step 2.10: Migrate Signing Keys\n\n**Decision tree for signing key migration:**\n\n1. **Can you restart all client applications and APIs?** → Remove old key, use automatic key management. All clients will fetch the new key from the discovery document.\n\n2. **Cannot restart everything?** → Export the old signing key and configure it alongside automatic key management so existing tokens remain valid during the transition period.\n\n```csharp\n// Transitional: keep old key while automatic key management creates new keys\nbuilder.Services.AddIdentityServer()\n .AddSigningCredential(existingRsaKey) // old key for validation\n // automatic key management handles new token signing\n```\n\n### Step 2.11: Verify Authentication Scheme Compatibility\n\nThird-party authentication handlers registered in your IdentityServer4 project may need updates for the target ASP.NET Core version. The Migration Analysis Tool (Step 0) lists all registered authentication schemes and flags non-Microsoft, non-IdentityServer4 handlers.\n\n**Common handlers that need updates:**\n\n| Old Handler | Action |\n|-------------|--------|\n| WS-Federation (`Microsoft.AspNetCore.Authentication.WsFederation`) | Update NuGet package to match target .NET version |\n| SAML2P (e.g., Sustainsys.Saml2, ITfoxtec.Identity.Saml2) | Update to a version compatible with .NET 10; note that Duende v8 has built-in SAML 2.0 IdP support (see `identityserver-saml` skill) |\n| Social providers (Google, Facebook, Twitter, etc.) | Update `Microsoft.AspNetCore.Authentication.*` packages to match target framework |\n| Custom `IAuthenticationHandler` implementations | Verify interface compatibility — `AuthenticateAsync`, `ChallengeAsync`, `ForbidAsync` signatures are stable, but constructor-injected types may have changed |\n\nAfter migration, verify all external login flows work end-to-end. Missing or incompatible handlers will cause runtime errors when users attempt to authenticate via those schemes.\n\n### Step 2.12: Update UI Templates\n\nNot all IdentityServer4 projects have a UI layer. Projects that only configure stores, clients, and resources (e.g., headless API-only hosts or database migration utilities) have **no UI to migrate** — skip this step entirely.\n\nIf your project includes the IdentityServer4 Quickstart UI (login, logout, consent, error pages — typically MVC controllers with Razor views under `Views/` and `Controllers/`, or Razor Pages under `Pages/`), those templates must be updated. The IdentityServer4 Quickstart UI and the Duende IdentityServer UI templates have diverged significantly since 2018.\n\n#### What Changed in the UI\n\n- **Controller → Razor Pages migration**: Duende's newer templates use Razor Pages (`Pages/`) instead of MVC controllers (`Controllers/` + `Views/`). Your existing MVC-based UI will still compile and work after the namespace update, but you will miss newer UI flows.\n- **New pages**: Duende templates include pages for device flow authorization, CIBA (Client-Initiated Backchannel Authentication), dynamic identity provider management, and server-side session management that did not exist in IdentityServer4.\n- **API changes in view code**: Razor views that use `IIdentityServerInteractionService` must be updated for v4 API changes (e.g., `request.ClientId` → `request.Client.ClientId`, `ScopesConsented` → `ScopesValuesConsented`, `GetAllUserConsentsAsync` → `GetAllUserGrantsAsync`).\n- **Namespace updates in views**: Any `@using IdentityServer4` directives in `.cshtml` files must become `@using Duende.IdentityServer`. Check `_ViewImports.cshtml` and individual view files.\n- **Updated CSS and JavaScript**: Layout, styling, and client-side scripts have been refreshed.\n\n#### Recommended Approaches\n\n1. **Preferred: Start fresh** with Duende templates and port your customizations:\n ```bash\n dotnet new install Duende.Templates\n dotnet new duende-is-ui\n ```\n This scaffolds the current Duende UI pages into your project. Diff the output against your existing UI to identify where your customizations belong.\n\n2. **Alternative: Incremental** — use a diff tool to compare your current UI with the Duende templates and apply changes surgically. This is practical when your UI has heavy customizations and starting fresh would lose too much work.\n\n3. **Minimum viable update** (for projects that just need to compile): Update namespaces in all `.cshtml` files and `_ViewImports.cshtml`, fix v4 API changes in controllers/page models, and defer the full UI refresh. This gets you running but leaves you on the older layout.\n\n---\n\n## Migration Checklist\n\nUse this checklist to track your migration progress:\n\n- [ ] **Run the Migration Analysis Tool** (Step 0) to get a baseline report of your current configuration\n- [ ] **Determine starting version** — v3.x requires Stage 1 first; v4.x goes directly to Stage 2\n- [ ] **Inventory clients** — count interactive vs. non-interactive for license edition selection\n- [ ] **Update .NET target framework** to `net10.0`\n- [ ] **Replace NuGet packages** — `IdentityServer4.*` → `Duende.IdentityServer.*`\n- [ ] **Update all namespaces** — `IdentityServer4` → `Duende.IdentityServer`; `IdentityModel` → `Duende.IdentityModel`\n- [ ] **Fix breaking API changes** (v3 → v4 if applicable)\n- [ ] **Convert to minimal hosting** (if migrating from `netcoreapp3.1`)\n- [ ] **Preserve issuer URI** — set `IssuerUri` explicitly to match existing deployment\n- [ ] **Configure license key** via configuration/secret manager\n- [ ] **Remove `AddDeveloperSigningCredential`** — use automatic key management or static credential\n- [ ] **Create and apply database migrations** for both `ConfigurationDbContext` and `PersistedGrantDbContext`\n- [ ] **Configure data protection** with explicit `SetApplicationName` and persistent key storage\n- [ ] **Migrate or rotate signing keys**\n- [ ] **Verify authentication scheme compatibility** — update third-party auth handlers for new .NET version\n- [ ] **Update UI templates** — fresh start or incremental diff (skip if project has no UI layer)\n- [ ] **Verify discovery document** at `/.well-known/openid-configuration`\n- [ ] **Test token issuance and validation** end-to-end\n- [ ] **Check application logs** for warnings or errors\n\n---\n\n## Common Migration Issues\n\n**`IdentityServer4` namespace not found after package update** — You replaced the NuGet package but didn't update namespaces. Search and replace `using IdentityServer4` with `using Duende.IdentityServer` across all files.\n\n**`Scope` type not found in v4** — In v4, `Scope` was removed as a nested type. API scopes are now top-level `ApiScope` objects. Update `ApiResource.Scopes` from `Scope` objects to string scope names.\n\n**EF migration drops and recreates tables (v3 → v4)** — The auto-generated migration will destroy data. Use the custom delta SQL script approach described in Step 1.4.\n\n**Data protection keys invalid after .NET upgrade** — The default application discriminator changed between .NET versions. Set `SetApplicationName()` explicitly to maintain key continuity.\n\n**Data protection keys lost on restart** — If no persistent key repository is configured, data protection uses an ephemeral in-memory store. All encrypted data (persisted grants, cookies) becomes unreadable after restart. Configure `PersistKeysToDbContext`, `PersistKeysToFileSystem`, or `PersistKeysToAzureBlobStorage`.\n\n**Issuer URI changed after migration** — If the `iss` claim in tokens no longer matches what clients/APIs expect, all existing tokens and trust relationships break. Set `options.IssuerUri` explicitly to match the value from your old `/.well-known/openid-configuration`.\n\n**Third-party authentication handler fails at runtime** — External auth handlers (WS-Fed, SAML2P, social providers) compiled against older ASP.NET Core versions may fail to load. Update their NuGet packages to versions compatible with your target .NET version.\n\n**Discovery document shows HTTP instead of HTTPS** — If behind a reverse proxy, configure forwarded headers. This is not migration-specific but commonly surfaces during deployment changes.\n\n**`AddDeveloperSigningCredential` method not found** — This method still exists in Duende but is intended for development only. For production, use automatic key management or a static signing credential.\n\n**`IsPkceClientAsync` method not found** — This was removed in v4. Use `context.IsNativeClient()` or check `request.Client.RequirePkce` directly.\n\n**`ConsentResponse.ScopesConsented` property not found** — Renamed to `ScopesValuesConsented` in v4.\n\n**Existing persisted grants fail to decrypt after migration** — Ensure ASP.NET Core Data Protection keys from the old deployment are still available. Data protection encrypts the `Data` column in persisted grants. If keys are lost, stored grants become unreadable.\n\n---\n\n## Version Compatibility Reference\n\n| IdentityServer Version | .NET Version | EF Core Version |\n|------------------------|-------------|-----------------|\n| IdentityServer4 v3.x | .NET Core 3.1 | EF Core 3.1 |\n| IdentityServer4 v4.x | .NET Core 3.1 / .NET 5 | EF Core 3.1 / 5.0 |\n| Duende IdentityServer v5.x | .NET 5 / .NET 6 | EF Core 5.0 / 6.0 |\n| Duende IdentityServer v6.x | .NET 6 / .NET 7 | EF Core 6.0 / 7.0 |\n| Duende IdentityServer v7.x | .NET 8 | EF Core 8.0 |\n| Duende IdentityServer v8.x | .NET 10 | EF Core 10.0 |\n\n---\n\n## Resources\n\n- [Migration Analysis Tool](https://docs.duendesoftware.com/identityserver/upgrades/identityserver4-upgrade-analysis/) — pre-migration configuration inspector\n- [Official Duende Migration Guide: IdentityServer4 to Duende v8](https://docs.duendesoftware.com/identityserver/upgrades/identityserver4-to-duende-identityserver-v8/)\n- [UpgradeSample-IdentityServer4-v3 (reference project)](https://github.com/DuendeSoftware/UpgradeSample-IdentityServer4-v3)\n- [Duende IdentityServer Upgrade Overview](https://docs.duendesoftware.com/identityserver/upgrades/)\n- [Microsoft ASP.NET Core Migration Guides](https://learn.microsoft.com/en-us/aspnet/core/migration/)\n- [Duende IdentityServer Templates](https://www.nuget.org/packages/Duende.Templates)\n- Related skill: `identityserver-hosting-setup` — setting up and hosting Duende IdentityServer\n- Related skill: `identityserver-stores` — EF Core store configuration and migrations\n- Related skill: `identityserver-configuration` — client and resource configuration\n- Related skill: `identityserver-key-management` — signing key management and rotation\n- Related skill: `identityserver-upgrade-v7-to-v8` — additional v8 breaking changes (HybridCache, TimeProvider, CancellationToken on all interfaces)\n"
}SHA-256: e6c0bd67cbeb71d911016da2736b25fde66e0d0c44e6fc4cd3f1977fa7410f8c