| title | IdentityServer4 to Duende IdentityServer v7.4 | ||||
|---|---|---|---|---|---|
| sidebar |
|
import { Code } from "@astrojs/starlight/components"; import { Steps } from "@astrojs/starlight/components"; import { Tabs, TabItem } from "@astrojs/starlight/components";
This upgrade guide covers upgrading from IdentityServer4 to Duende IdentityServer v7.4. IdentityServer4 reached its end of life (EOL) on December 13, 2022. It is strongly advised to migrate to Duende IdentityServer.
:::tip[IdentityServer4 Upgrade Analysis] Start your upgrade with this step-by-step migration guide, and use our automated IdentityServer4 Upgrade Analysis tool to help identify important aspects of your upgrade to Duende IdentityServer.
We also offer a free IdentityServer4 upgrade assessment to walk you through your upgrade path. :::
Depending on your current version of IdentityServer4, different steps may be required.
You can determine the version of IdentityServer4 by running the dotnet list command at the root of your IdentityServer host project, or using NuGet tooling in Visual Studio or JetBrains Rider.
{/* prettier-ignore */} <Code code={'dotnet list package | sls "IdentityServer4"'} lang="bash" title="Terminal" /> <Code code={"dotnet list package | grep IdentityServer4"} lang="bash" title="Terminal" />
This command will print a list of packages you’re using in your solution, along with their version.
> IdentityServer4 3.1.4 3.1.4
> IdentityServer4.EntityFramework 3.1.4 3.1.4Depending on the package version shown, your next steps will be different:
- If you are on IdentityServer v3.x, we recommend first upgrading to IdentityServer4 v4.x, and then to Duende IdentityServer. The configuration object model changed between the two major versions of IdentityServer4, and we recommend upgrading step-by-step.
- If you are on IdentityServer v4.x, you can immediately upgrade to Duende IdentityServer.
:::note
Check your current host project's UI elements against the latest templates in Duende.Templates to ensure
you’re using the latest recommendations and best practices.
:::
IdentityServer has always been a framework that championed customization and making the implementation your own, and we treat templates as a starting point in your journey to implementing an OIDC and OAuth server.
During the development of IdentityServer4, the UI templates saw several changes between the years of 2018 to 2021.
We recommend two approaches to upgrading your UI elements of your IdentityServer host project:
- Start with the latest templates and port your customizations to the new templates.
- Use a code comparison tool to identify the changes you need to make to your templates.
The first approach is the easiest, but it requires you to make changes to your project. The second approach is more involved, but it allows you to make changes to your project in a more controlled manner.
The most straightforward upgrade path is to first update to the latest version of IdentityServer4 v4, and then continue to the latest Duende IdentityServer.
:::tip[Sample project] We have a sample project available on GitHub, which contains database migration scripts for these changes. :::
Between IdentityServer4 v3.x and v4.x, the configuration object model was updated:
- The relation between
ApiResourcesandApiScopeswas changed from parent-child to many-to-many. - A number of configuration types were renamed:
ApiPropertiestoApiResourcePropertiesApiSecretstoApiResourceSecretsIdentityClaimstoIdentityResourceClaimsIdentityPropertiestoIdentityResourcePropertiesApiScopestoApiResourceScopes
IdentityServer4 projects that use the IdentityServer4.EntityFramework package or implement their own stores will need to update their code and/or database to reflect these changes.
:::caution Database changes will need to be done using a custom migration script, as using the default Entity Framework Core migration will result in data loss. We'll look at this later in this upgrade guide. :::
Update the IdentityServer4 dependencies in your IdentityServer host project to version 4.1.2.
- <PackageReference Include="IdentityServer4" Version="3.1.4" />
+ <PackageReference Include="IdentityServer4" Version="4.1.2" />Make sure to change the version number for all IdentityServer4 packages, including IdentityServer4.EntityFramework and IdentityServer4.AspNetIdentity.
It's likely some code changes will be required, especially when using the IdentityServer UI templates. Visual Studio and JetBrains Rider provide great code completion that can be of help here.
:::tip[Use the new Razor Pages templates] If you have not made customizations to the IdentityServer UI templates, consider replacing them with the new Razor Pages-based templates as shown in the quickstart tutorials. :::
A couple of compilation errors and required changes you may encounter:
-
When configuring scopes in memory, you need to register the scopes on startup.
builder.Services.AddIdentityServer() .AddInMemory... + .AddInMemoryApiScopes(Config.ApiScopes) -
The
IIdentityServerInteractionService.GetAllUserConsentsAsyncmethod was renamed toIIdentityServerInteractionService.GetAllUserGrantsAsync -
ConsentResponse.Deniedwas removed. Use theDenyAuthorizationAsyncinstead:- await _interaction.GrantConsentAsync(context, ConsentResponse.Denied); + await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied);
-
No overload method
SignInAsynctakes N arguments. TheHttpContext.SignInAsyncsignature changed:// issue authentication cookie with subject ID and username - await HttpContext.SignInAsync(user.SubjectId, user.Username, props); + var isuser = new IdentityServerUser(user.SubjectId) + { + DisplayName = user.Username + }; + + await HttpContext.SignInAsync(isuser, props);
-
AuthorizationRequestdoesn't contain definition forClientId:- var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId); + var client = await _clientStore.FindEnabledClientByIdAsync(request.Client.ClientId);
-
AuthorizationRequestdoesn't contain definition forScopesRequested:- var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested); + var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ValidatedResources.RawScopeValues);
-
IClientStoredoesn't contain definition forIsPkceClientAsync:- if (await _clientStore.IsPkceClientAsync(context.ClientId)) + if (context.IsNativeClient())
-
The name
ProcessLoginCallbackForOidcdoesn’t exist in the current context:- ProcessLoginCallbackForOidc(result, additionalLocalClaims, localSignInProps); - ProcessLoginCallbackForWsFed(result, additionalLocalClaims, localSignInProps); - ProcessLoginCallbackForSaml2p(result, additionalLocalClaims, localSignInProps); + ProcessLoginCallback(result, additionalLocalClaims, localSignInProps);
-
ConsentResponsedoes not contain a definition forScopesConsented:grantedConsent = new ConsentResponse { RememberConsent = model.RememberConsent, - ScopesConsented = scopes.ToArray() + ScopesValuesConsented = scopes.ToArray() };
If you are using the IdentityServer4.EntityFramework package to store configuration and operational data in a database,
you'll need to create two database migrations that update the database schema.
Note that you may want to change the database migration paths in the examples below to reflect your project structure.
:::caution Make sure to verify the database migrations as part of your IdentityServer4 upgrade, to ensure all configuration and operational data remains in place. :::
For the operational data, you can create and apply an Entity Framework Core migration that targets the PersistedGrantDbContext database context.
{/* prettier-ignore */}
-
Create the migration:
dotnet ef migrations add Grants_v4 -c PersistedGrantDbContext -o Migrations/PersistedGrantDb
-
Apply the migration to your database:
dotnet ef database update -c PersistedGrantDbContext
For your configuration data, the conversation from the old schema to the new will need to be performed by applying a custom SQL script.
We'll start with creating a migration that targets the ConfigurationDbContext database context:
{/* prettier-ignore */}
-
Create the migration:
dotnet ef migrations add Config_v4 -c ConfigurationDbContext -o Migrations/ConfigurationDb
You will see a message "An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy." in the output. To avoid data loss, the migration will need to be updated.
-
To ensure no data is lost, make sure to include the
ConfigurationDb_v4_delta.sqlscript in your project.You can add the script as an embedded resource by updating the
.csprojfile:<ItemGroup> <EmbeddedResource Include="ConfigurationDb_v4_delta.sql" /> </ItemGroup>
:::note[Update the SQL script for your database type] The
ConfigurationDb_v4_delta.sqlfile assumes you are using SQL Server. If a different database server type is used for your IdentityServer host, you'll need to update the SQL script to use the correct syntax. ::: -
Modify the migration class that was just created and replace it with the following code:
using System.IO; using Microsoft.EntityFrameworkCore.Migrations; namespace IdentityServerMigrationSample.Migrations.ConfigurationDb { public partial class Config_v4 : Migration { protected override void Up(MigrationBuilder migrationBuilder) { var assembly = typeof(Program).Assembly; using (var s = assembly.GetManifestResourceStream("IdentityServerMigrationSample.ConfigurationDb_v4_delta.sql")) { using (StreamReader sr = new StreamReader(s)) { var sql = sr.ReadToEnd(); migrationBuilder.Sql(sql); } } } protected override void Down(MigrationBuilder migrationBuilder) { } } }
-
Apply the migration to your database:
dotnet ef database update -c ConfigurationDbContext
Your database schema should now be updated. You can verify all data is intact, using the queries outlined in query_v4.sql.
These queries may return a lot of data, consider adding a TOP clause if you want to only sample some of the migrated data.
View SQL queries to validate migration succeeded
```sql title="query_v4.sql"
SELECT * FROM __EFMigrationsHistory
SELECT * FROM ApiResourceClaims
SELECT * FROM ApiResourceProperties
SELECT * FROM ApiResources
SELECT * FROM ApiResourceScopes
SELECT * FROM ApiResourceSecrets
SELECT * FROM ApiScopeClaims
SELECT * FROM ApiScopes
SELECT * FROM ApiScopeProperties
SELECT * FROM ClientClaims
SELECT * FROM ClientCorsOrigins
SELECT * FROM ClientGrantTypes
SELECT * FROM ClientIdPRestrictions
SELECT * FROM ClientPostLogoutRedirectUris
SELECT * FROM ClientProperties
SELECT * FROM ClientRedirectUris
SELECT * FROM Clients
SELECT * FROM ClientScopes
SELECT * FROM ClientSecrets
SELECT * FROM DeviceCodes
SELECT * FROM PersistedGrants
SELECT * FROM IdentityResourceClaims
SELECT * FROM IdentityResourceProperties
SELECT * FROM IdentityResources
```
With the migration to IdentityServer v4.x complete, you can upgrade to Duende IdentityServer.
Upgrading from IdentityServer4 v4.x to Duende IdentityServer consists of several tasks: updating the target .NET version, updating NuGet packages, and performing database schema migrations.
In this guide, we'll update to .NET 10 LTS.
Update the IdentityServer target framework:
- <TargetFramework>netcoreapp3.1</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>Some of your project dependencies may need updating as part of changing the target framework.
For example, Microsoft.EntityFrameworkCore.SqlServer and Microsoft.AspNetCore.Authentication.Google will need to be updated to the version matching your target framework.
:::tip[Updating .NET and ASP.NET Core] Depending on the amount of customization in your IdentityServer4 host and UI templates, there may be code changes needed based on updates to .NET and ASP.NET Core. Make sure to consult the ASP.NET Core migration guides provided by Microsoft.
Breaking changes in .NET
* [Breaking changes in .NET Core 3.1](https://learn.microsoft.com/en-us/dotnet/core/compatibility/3.1)
* [Breaking changes in .NET 5](https://learn.microsoft.com/en-us/dotnet/core/compatibility/5.0)
* [Breaking changes in .NET 6](https://learn.microsoft.com/en-us/dotnet/core/compatibility/6.0)
* [Breaking changes in .NET 7](https://learn.microsoft.com/en-us/dotnet/core/compatibility/7.0)
* [Breaking changes in .NET 8](https://learn.microsoft.com/en-us/dotnet/core/compatibility/8.0)
* [Breaking changes in .NET 9](https://learn.microsoft.com/en-us/dotnet/core/compatibility/9.0)
* [Breaking changes in .NET 10](https://learn.microsoft.com/en-us/dotnet/core/compatibility/10.0)
ASP.NET Core migration guides
* [Migrate from ASP.NET Core 3.1 to 6.0](https://learn.microsoft.com/en-us/aspnet/core/migration/31-to-60)
* [Migrate from ASP.NET Core 6.0 to 7.0](https://learn.microsoft.com/en-us/aspnet/core/migration/60-70)
* [Migrate from ASP.NET Core 7.0 to 8.0](https://learn.microsoft.com/en-us/aspnet/core/migration/70-80)
* [Migrate from ASP.NET Core 8.0 to 9.0](https://learn.microsoft.com/en-us/aspnet/core/migration/80-90)
* [Migrate from ASP.NET Core 9.0 to 10.0](https://learn.microsoft.com/en-us/aspnet/core/migration/90-to-100)
Update the IdentityServer4 dependencies in your IdentityServer host project to Duende IdentityServer.
- <PackageReference Include="IdentityServer4" Version="4.1.2" />
+ <PackageReference Include="Duende.IdentityServer" Version="7.4.3" />You'll need to make a similar change for all IdentityServer4 packages, including IdentityServer4.EntityFramework and IdentityServer4.AspNetIdentity. For example:
- <PackageReference Include="IdentityServer4.EntityFramework" Version="4.1.2" />
+ <PackageReference Include="Duende.IdentityServer.EntityFramework" Version="7.4.3" />The IdentityModel package was renamed to Duende IdentityModel and needs updating if you reference it directly:
- <PackageReference Include="IdentityModel" Version="x.y.z" />
+ <PackageReference Include="Duende.IdentityModel" Version="8.0.0" />In your project source code, replace all IdentityServer4 namespace usages with Duende.IdentityServer:
- using IdentityServer4;
- using IdentityServer4.Models;
+ using Duende.IdentityServer;
+ using Duende.IdentityServer.Models;Replace all IdentityModel namespace usages with Duende.IdentityModel:
- using IdentityModel;
+ using Duende.IdentityModel;If you’re using fully-qualified names in your code, those will need to be updated as well.
In your application startup code, typically found in the ConfigureServices method in Startup.cs, consider removing AddDeveloperSigningCredential.
You can use Duende IdentityServer's built-in manual or automatic key management instead.
Whether you are using a database or a custom store implementation for your configuration and operational data, you'll need to make some changes. The exact steps involved in updating your data store will depend on your implementation details.
In this section, we'll look at updating the database schema based on the stores provided in the Duende.IdentityServer.EntityFramework package:
-
Create a new
Keystable for the automatic key management feature in the operational database. -
Create a new
RequireResourceIndicatorboolean column on theApiResourcestable in the configuration database. -
Create a new index on the
ConsumedTimecolumn in thePersistedGrantstable (more details). -
Create a new table called
IdentityProvidersfor storing the OIDC provider details (more details). -
Add missing columns for created, updated, etc. to EF entities (more details).
-
Add unique constraints to EF tables where duplicate records are not allowed (more details).
-
The server-side sessions feature requires a new table (more details).
-
The session coordination feature adds a column to the
Clientstable (more details). -
Improve primary keys on the persisted grants table (more details).
-
Add new properties to the
Duende.IdentityServer.Models.Clientmodel:InitiateLoginUriis a nullable string used for Third Party Initiated Login.RequireDPoPis a non-nullable boolean flag that controls if a client is required to use DPoP.DPoPValidationModeis a non-nullable column that controls the DPoP validation mechanism. Existing clients that aren’t using DPoP can set its value to0.DPoPClockSkewis a non-nullable timespan that controls how much clock skew is allowed for a particular DPoP client. Existing clients that aren’t using DPoP can set its value to a timespan of length ``0.
-
Two new properties have been added to the
Clientmodel:Client.RequirePushedAuthorizationis a new boolean property that controls if this client requires pushed authorization requests (PAR). It is safe to initialize this column tofalsefor existing clients, which will mean that the global PAR configuration will be used.Client.PushedAuthorizationLifetimeis a new nullable integer property that controls the lifetime of pushed authorization requests (in seconds) for a client. It is safe to initialize this column tonullfor existing clients, which means the global value is used.
-
A new
PushedAuthorizationRequesttable has been added to store pushed authorization requests.
You'll need to create two database migrations that update the database schema: one that targets the PersistedGrantDbContext (for operational data), and one that targets the ConfigurationDbContext (for configuration data).
Note that you may want to change the database migration paths in the examples below to match your project structure.
{/* prettier-ignore */}
-
Create the migrations for the operational and configuration database context:
dotnet ef migrations add UpdateToDuende_v7_0 -c PersistedGrantDbContext -o Data/Migrations/IdentityServer/PersistedGrantDb dotnet ef migrations add UpdateToDuende_v7_0 -c ConfigurationDbContext -o Data/Migrations/IdentityServer/ConfigurationDb
:::note You may see a warning "An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.". The column length for redirect URIs (for both login and logout) was reduced from 2000 to 400 to overcome database index size limits. Unless you are using redirect URIs greater than 400 characters, this should not affect you. :::
-
Apply the migrations to your database:
dotnet ef database update -c PersistedGrantDbContext dotnet ef database update -c ConfigurationDbContext
:::tip[Determine if you are using a custom signing key]
In Startup.cs, look for a call to AddSigningCredential() that uses key material such as an X509Certificate2.
:::
Client apps and APIs typically cache the key material published from IdentityServer's discovery document. When upgrading, consider how those applications will handle an upgraded token server with a new and different signing key.
- If you can restart all client apps and APIs that depend on your current signing key, you can remove the old signing key and start to use automatic key management. A restart reloads the discovery document and the new signing key.
- If you can not restart client apps and APIs, check the manual and automatic key rotation topics to learn how to announce new signing key material while still supporting the old signing key for a period of time.
If your IdentityServer4 implementation is using a signing key, consider using automatic key management.
:::note This feature is part of the Duende IdentityServer Business and Enterprise Edition. :::
Duende IdentityServer depends on ASP.NET Data Protection to encrypt and sign data using keys managed by ASP.NET.
As part of your migration, verify the application name is set in your Data Protection configuration:
builder.Services.AddDataProtection()
.PersistKeysTo...()
.ProtectKeysWith...()
.SetApplicationName("IdentityServerXYZ");If an application name is set, you can skip this section.
Data Protection keys are isolated by application name, to prevent multiple applications from sharing encryption keys.
If no application name is configured, ASP.NET Data Protection uses the content root path of the IdentityServer host as the application name. As a consequence, if your content root path changes, the default settings for data protection will prevent you from using your old data protection keys.
Between different .NET versions, this default setting has changed:
| Version | Default |
|---|---|
| .NET 3.1 | Content root path without directory separator suffix |
| .NET 5 | Content root path without directory separator suffix |
| .NET 6 | Content root path (normalized with directory separator suffix) |
| .NET 7+ | Content root path without directory separator suffix |
Your application name might change (and existing data protection keys may become invalid) if you’re currently targeting .NET 6 and don’t have the application name set explicitly.
To prevent this from happening, you can explicitly set the application name to the content root path without the directory separator character, as documented on Microsoft Learn.
:::tip[Getting the current application name] In your current (pre-upgraded) IdentityServer version, you can query the application name used and set it explicitly in your upgraded deployment:
// ...
var app = builder.Build();
// ...
var applicationName = "";
var applicationDiscriminatorService = app.Services.GetService<IApplicationDiscriminator>();
if (applicationDiscriminatorService != null)
{
applicationName = applicationDiscriminatorService.Discriminator;
}
var dataProtectionOptions = app.Services.GetService<IOptions<DataProtectionOptions>>();
if (dataProtectionOptions != null && dataProtectionOptions.Value.ApplicationDiscriminator != null)
{
dataProtectionApplicationDiscriminator = dataProtectionOptions.Value.ApplicationDiscriminator;
}
// applicationName is now the (auto-generated?) application name
// - you can use its value in your upgraded IdentityServer version:::
Congratulations! Your upgrade is complete.
Make sure to validate and test your Duende IdentityServer is working as expected, and check integrations with your client applications and APIs.
:::note IdentityServer is free for development, testing, and personal projects, but production use requires a license. :::
Duende IdentityServer is a Software Development Kit (SDK) that you can use to build your own identity and access management solutions (IAM). Being an SDK, there is a lot of potential for customization during the implementation. Depending on your specific project, breaking changes might affect your use of IdentityServer.
As part of your upgrade from IdentityServer4 to Duende IdentityServer, we recommend reviewing these breaking changes to understand if any of them affect you.
- Quickstart UI updated to use Razor Pages
- Addition of cancellation token to store APIs
- Store DbContext constructors to support DbContext pooling
- CustomRedirectResult returnUrl changes
- Add missing columns for created, updated, etc. to EF entities
- Add unique constraints to EF tables where duplicate records not allowed
- Added grant handle versioning suffix
- Many HttpContext extensions marked obsolete
- New APIs to the ICache interface
- New Client columns for CIBA
- openid no longer implicit in OidcProvider scope collection
- JwtRequestValidator signature changes
- Changes in AppAuth URL validator for logout
- Use of EmailClaimType option in ASP.NET Identity integration
ClientConfigurationStorenow usesIConfigurationDbContextto allow for customization. If you have a customized Entity Framework Core-based store, you may need to update your constructors.- The
SendLogoutNotificationAsyncmethod has been removed from theDefaultBackChannelLogoutServiceclass - Client
Secretis now required for Clients withClientCredentialsgrant