--- name: identityserver-upgrade-v7-to-v8 description: "Migrating Duende IdentityServer from v7.4 to v8.0: breaking changes, API replacements (ICache→HybridCache, IClock→TimeProvider), CancellationToken additions, EF migrations, and step-by-step upgrade guide." invocable: false --- # Upgrading IdentityServer v7 to v8 ## When to Use This Skill - Upgrading a Duende IdentityServer project from v7.4 to v8.0 - Fixing build errors after updating NuGet packages to v8 - Migrating custom stores/services to new v8 interfaces - Running EF Core database migrations for v8 (SAML tables) - Replacing deprecated APIs (ICache, IClock, IAuthorizationParametersMessageStore) ## Core Principles - v8.0 requires **.NET 10** — update TFM before anything else - All breaking changes are compile-time errors (no silent behavior changes) - Migration is mechanical — find/replace patterns work for most changes - Run EF migrations even if you don't use SAML (schema must match) - **Always check the latest stable 8.x package version** on [NuGet](https://www.nuget.org/packages/Duende.IdentityServer) before upgrading — do not hardcode `8.0.1`; use whatever the latest stable (non-prerelease) 8.x version is at the time of the upgrade. Docs: https://docs.duendesoftware.com/identityserver/upgrades/v7_4-to-v8_0/ ## Step-by-Step Migration ### 1. Update Target Framework ```xml net8.0 net10.0 ``` ### 2. Update NuGet Packages Check [NuGet](https://www.nuget.org/packages/Duende.IdentityServer) for the latest stable 8.x version. At time of writing, that is `8.0.1`, but use whatever is current: ```xml ``` ### 3. Run EF Database Migrations Two migrations are required — one for the Configuration Store and one for the Operational Store: ```bash # Configuration Store — adds 7 SAML-related tables dotnet ef migrations add Update_DuendeIdentityServer_v8_0 \ -c ConfigurationDbContext -o Migrations/ConfigurationDb dotnet ef database update -c ConfigurationDbContext # Operational Store — adds 3 SAML session tables dotnet ef migrations add Update_DuendeIdentityServer_v8_0_Saml \ -c PersistedGrantDbContext -o Migrations/PersistedGrantDb dotnet ef database update -c PersistedGrantDbContext ``` Both are required even if you don't use SAML (schema must match). ### 4. Replace ICache with HybridCache ```csharp // ❌ Before (v7) public class MyService { private readonly ICache _cache; public MyService(ICache cache) => _cache = cache; public async Task GetAsync(string key) { return await _cache.GetOrAddAsync(key, TimeSpan.FromMinutes(5), () => LoadFromDbAsync(key)); } } // ✅ After (v8) — use Microsoft HybridCache public class MyService { private readonly HybridCache _cache; public MyService([FromKeyedServices("ConfigurationStoreCache")] HybridCache cache) => _cache = cache; public async Task GetAsync(string key, CancellationToken ct) { return await _cache.GetOrCreateAsync(key, async token => await LoadFromDbAsync(key, token), new HybridCacheEntryOptions { Expiration = TimeSpan.FromMinutes(5) }, cancellationToken: ct); } } ``` Key: use keyed service `"ConfigurationStoreCache"` (`ServiceProviderKeys.ConfigurationStoreCache`). `CachingOptions.CacheLockTimeout` is obsolete. ### 5. Replace IClock with TimeProvider ```csharp // ❌ Before (v7) public class MyService { private readonly IClock _clock; public MyService(IClock clock) => _clock = clock; public DateTime Now => _clock.UtcNow.UtcDateTime; } // ✅ After (v8) public class MyService { private readonly TimeProvider _timeProvider; public MyService(TimeProvider timeProvider) => _timeProvider = timeProvider; public DateTime Now => _timeProvider.GetUtcNow().UtcDateTime; } ``` Note: `GetUtcNow()` (method) replaces `UtcNow` (property). ### 6. Add CancellationToken to All Async Interfaces All store and service interfaces now require `CancellationToken ct` as the last parameter: ```csharp // ❌ Before (v7) public Task FindClientByIdAsync(string clientId) // ✅ After (v8) public Task FindClientByIdAsync(string clientId, CancellationToken ct) ``` Affected interfaces include: `IClientStore`, `IResourceStore`, `IPersistedGrantStore`, `IDeviceFlowStore`, `ICorsPolicyService`, `IProfileService`, and all custom stores/services. Also: `ICancellationTokenProvider` is removed entirely. ### 7. Add GetAllClientsAsync to IClientStore ```csharp // ✅ New required method public IAsyncEnumerable GetAllClientsAsync(CancellationToken ct) ``` Used by Financial-Grade Security features and conformance reports. ### 8. Update Refresh Token Service ```csharp // ❌ Before (v7) — individual parameters public Task CreateRefreshTokenAsync( ClaimsPrincipal subject, Token accessToken, Client client) // ✅ After (v8) — request objects public Task CreateRefreshTokenAsync(RefreshTokenCreationRequest request, CancellationToken ct) public Task UpdateRefreshTokenAsync(RefreshTokenUpdateRequest request, CancellationToken ct) ``` ### 9. Remove IAuthorizationParametersMessageStore ```csharp // ❌ Removed in v8 — use PAR (Pushed Authorization Requests) instead services.AddTransient(); // ✅ PAR is the replacement for passing large authorization parameters ``` ### 10. Fix Return Type Changes Nine interfaces changed `IEnumerable` → `IReadOnlyCollection`: ```csharp // ❌ Before public Task> FindApiScopesByNameAsync(IEnumerable scopeNames) // ✅ After public Task> FindApiScopesByNameAsync( IEnumerable scopeNames, CancellationToken ct) ``` ### 11. Fix DPoP Type Names ```csharp // ❌ Typo in v7 DPoPProofValidatonContext → DPoPProofValidationContext DPoPProofValidatonResult → DPoPProofValidationResult ``` ### 12. Update Licensing Code ```csharp // ❌ Before (v7) var license = IdentityServerLicense.Current; var edition = summary.LicenseEdition; // ✅ After (v8) var info = LicenseInformation.Current; // from Duende.IdentityServer.Licensing var skus = summary.EntitledSkus; // collection replaces single edition ``` #### New v8 License Key Format - v8 introduced a **new license key file format**: the v8 key is a signed **JWT carrying a `kid` header**. - A **v7/earlier key still works with v8 core** — no new purchase is needed to run v8 core on an existing key. - A **v8 key does NOT work on v7/earlier OR on the BFF Security Framework runtime**. It fails signature validation with Microsoft.IdentityModel error: - `IDX10503: Signature validation failed. Token does not have a kid.` - That exact error is the tell-tale sign of a **v8 key loaded into a v7 or BFF runtime**. - **Add-ons require a v8-format key in production**: using **SAML** or **Duende User Management** in production on v8 REQUIRES a new v8-format license key. Older-format keys run v8 core, but not these add-ons in production. #### Runtime License Enforcement Changed (behavioral reversal) v8 validates feature usage at runtime. When a **license IS present but lacks the entitlement**, behavior splits into two tiers: | Tier | Behavior when unlicensed | Features | | ---- | ------------------------ | -------- | | A | **THROWS** during startup validation | Server-Side Sessions, Automatic Key Management, SAML (IdP and Service Provider) | | B | **LOGS a warning** (rate-limited ~once/5 min) | DPoP, Resource Isolation, CIBA, Dynamic Identity Providers, Financial-grade/Conformance, User Management | - If **NO license is configured** (local dev / non-prod), Tier-A features **downgrade to logging** instead of throwing. - **Guidance**: use your **production license key in lower environments** so entitlement gaps (e.g. Server-Side Sessions) surface before production. - **Contrast with v7 and earlier**: those versions **disabled** some features at runtime when unlicensed (Server-Side Sessions, DPoP, Resource Isolation, PAR, Dynamic Identity Providers, CIBA). **v8 no longer disables** — it logs or throws per the tiers above. #### Editions → Plans The product moved from fixed **Starter / Business / Enterprise** editions to generic **plans**. The old three editions are still honored for legacy/long-term customers only. The **Community edition remains**. Update any code or docs that hard-code "three editions" to reflect the plan model. ### 13. Update EF Identity Provider Store ```csharp // ❌ Before (v7) public IdentityProviderStore(IServiceProvider sp, ConfigurationDbContext ctx) // ✅ After (v8) — new required parameter public IdentityProviderStore( IServiceProvider sp, ConfigurationDbContext ctx, IIdentityProviderFactory factory) ``` ### 14. Rename AuthorizationError → InteractionError ```csharp // ❌ Before (v7) if (result.Error == AuthorizationError.LoginRequired) { } // ✅ After (v8) if (result.Error == InteractionError.LoginRequired) { } ``` Values remain the same: `AccessDenied`, `LoginRequired`, `InteractionRequired`. ### 15. Rename DenyAuthorizationAsync → DenyAuthenticationAsync ```csharp // ❌ Before (v7) await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied); // ✅ After (v8) — now accepts IAuthenticationContext (protocol-agnostic for OIDC/SAML) await _interaction.DenyAuthenticationAsync(context, InteractionError.AccessDenied); ``` ### 16. Rename ProfileDataRequestContext.Client → .Application ```csharp // ❌ Before (v7) var client = context.Client; // ✅ After (v8) var client = context.Application; ``` ### 17. Update ITokenValidator.ValidateAccessTokenAsync ```csharp // ❌ Before (v7) await _validator.ValidateAccessTokenAsync(token); // ✅ After (v8) — new expectedScope parameter await _validator.ValidateAccessTokenAsync(token, expectedScope: null, ct); ``` ### 18. Relocate PreviewFeatureOptions `PreviewFeatureOptions` and `IdentityServerOptions.Preview` are removed. Options relocated: ```csharp // ❌ Before (v7) options.Preview.EnableDiscoveryDocumentCache = true; options.Preview.DiscoveryDocumentCacheDuration = TimeSpan.FromMinutes(10); options.Preview.StrictClientAssertionAudienceValidation = true; // ✅ After (v8) options.Discovery.EnableDiscoveryDocumentCache = true; options.Discovery.DiscoveryDocumentCacheDuration = TimeSpan.FromMinutes(10); options.StrictClientAssertionAudienceValidation = true; // default changed to false! ``` ## Other Notable Changes - **NRT enabled**: All assemblies use nullable reference types. Fix nullable warnings. - **HTTP 303**: POST endpoint redirects now unconditionally use 303 (FAPI 2.0 compliance). - **`PersistedGrantFilter.ClientIds`/`Types`**: Now non-nullable with empty collection defaults. Replace null checks with `.Count > 0`. - **IUserSession**: Three new SAML session methods added (implement as no-op if not using SAML): - `AddSamlSessionAsync`, `GetSamlSessionListAsync`, `RemoveSamlSessionAsync` - **Log levels**: Secret validation failures changed from Error to Debug — update alerting to watch for Warning-level entries at endpoint level instead. - **Device flow consent**: "Remember My Decision" no longer offered — `RememberConsent` always `false` during device flow (RFC 8628 security). - **License key from IConfiguration**: IdentityServer now reads license key automatically from `Duende:IdentityServer:LicenseKey` or `Duende:LicenseKey` in configuration. - **`DPoPExtensions` → `DPoPServiceCollectionExtensions`**: Class renamed in JwtBearer package. - **Token cleanup performance**: When no `IOperationalStoreNotification` registered, uses single `ExecuteDeleteAsync` call (automatic improvement, no action needed). - **Orphaned grants revoked on session overwrite**: When server-side sessions enabled and session cookie reused by different user, previous user's grants are automatically revoked. ## Migration Checklist 1. ☐ Update TFM to `net10.0` 2. ☐ Update all Duende.* packages to latest stable 8.x (check [NuGet](https://www.nuget.org/packages/Duende.IdentityServer)) 3. ☐ Run EF migrations (both `ConfigurationDbContext` and `PersistedGrantDbContext`) 4. ☐ Replace `ICache` → keyed `HybridCache` 5. ☐ Replace `IClock` → `TimeProvider` 6. ☐ Add `CancellationToken` to all async store/service methods 7. ☐ Remove `ICancellationTokenProvider` references 8. ☐ Add `GetAllClientsAsync` to custom `IClientStore` (returns `IAsyncEnumerable`) 9. ☐ Update `IRefreshTokenService` implementations (request objects) 10. ☐ Remove `IAuthorizationParametersMessageStore` (use PAR) 11. ☐ Fix `IEnumerable` → `IReadOnlyCollection` return types 12. ☐ Fix DPoP type name typos 13. ☐ Update licensing references (`IdentityServerLicense` → `LicenseInformation`) 14. ☐ Rename `AuthorizationError` → `InteractionError` 15. ☐ Rename `DenyAuthorizationAsync` → `DenyAuthenticationAsync` 16. ☐ Rename `ProfileDataRequestContext.Client` → `.Application` 17. ☐ Update `ITokenValidator.ValidateAccessTokenAsync` calls (add `expectedScope` param) 18. ☐ Relocate `PreviewFeatureOptions` settings 19. ☐ Fix nullable reference type warnings 20. ☐ Test build and run ## Common Pitfalls 1. **Forgetting EF migration**: Even without SAML, the schema must be updated or EF will throw at runtime. 2. **HybridCache keyed service**: Must use `[FromKeyedServices("ConfigurationStoreCache")]` — plain `HybridCache` injection gets a different instance. 3. **CancellationToken propagation**: Don't pass `CancellationToken.None` everywhere — propagate from the method parameter for proper request cancellation. 4. **GetAllClientsAsync performance**: Return all clients from your store; used rarely but must be implemented. 5. **PAR migration**: If you used `IAuthorizationParametersMessageStore` for large auth requests, switch clients to use PAR (`require_pushed_authorization_requests`). 6. **`IDX10503` after dropping in a v8 key**: A v8-format license key (signed JWT with a `kid` header) fails signature validation on v7/earlier or the **BFF Security Framework runtime** with `IDX10503: Signature validation failed. Token does not have a kid.` Keep the v7-format key for those runtimes — it still works on v8 core; only SAML/User Management add-ons in production require the new v8-format key. 7. **Entitlement gaps surface late**: v8 no longer silently disables unlicensed features — Server-Side Sessions, Automatic Key Management, and SAML now **throw at startup** when a license is present but missing the entitlement. Run lower environments with the production license key to catch this before deploying. ## Related Skills - `identityserver-configuration` — IdentityServer host configuration and options - `identityserver-stores` — Store implementation patterns (affected by CancellationToken changes) - `identityserver-saml` — SAML 2.0 support (new in v8, requires EF migration) - `identityserver-usermanagement` — User Management (new in v8)