Duende IdentityServer v6.3 to v7.0

What’s New

IdentityServer v7.0 includes support for .NET 8, pushed authorization requests, OpenTelemetry metrics, cleanup job improvements, and many other fixes and enhancements. Please see our release notes for complete details.

Step 1: Update .NET Version

In your IdentityServer host project, update the version of the .NET framework. For example in your project file:

<TargetFramework>net6.0</TargetFramework>

would change to:

<TargetFramework>net8.0</TargetFramework>

Any NuGet packages that you are using that target an older version of .NET should also be updated. For example, the version of Microsoft.EntityFrameworkCore.SqlServer or Microsoft.AspNetCore.Authentication.Google should be updated. Depending on what your IdentityServer host project is using, there may or may not be code changes based on those updated dependencies.

Step 2: Update NuGet package

In your IdentityServer host project, update the version of the Duende.IdentityServer package. For example in your project file:

<PackageReference Include="Duende.IdentityServer" Version="6.3.0" />

would change to:

<PackageReference Include="Duende.IdentityServer" Version="7.0" />

Step 3: Update Database Schema

The schema updates required in this version are:

  • The server-side session entity in Duende.IdentityServer.EntityFramework now uses a 64-bit long as its primary key (previously was a 32-bit int).
  • Two new properties have been added to the client model for PAR support.
    • Client.RequirePushedAuthorization is a new boolean property that controls if this client requires PAR. PAR is required if either the global configuration is enabled or if the client’s flag is enabled (this can’t be used to opt out of the global configuration). It is safe to initialize this column to false for existing clients, which will mean that the global configuration will be used.
    • Client.PushedAuthorizationLifetime is a new nullable integer property that controls the lifetime of pushed authorization requests (in seconds) for a client. If this lifetime is set, it takes precedence over the global configuration. It is safe to initialize this column to null for existing clients, which means the global configuration is used.
  • A new table has been added to store pushed authorization requests. This new table contains a hashed identifier, the pushed parameters (as a string, serialized and data protected), and the expiration time of the request.

IdentityServer is abstracted from the data store on multiple levels, so the steps involved in updating your data store will depend on your implementation details.

Custom Store Implementations

The core of IdentityServer is written against the store interfaces, which abstract all the implementation details of actually storing data. If your IdentityServer implementation includes a custom implementation of those stores, then you will have to determine how best to include the changes in the model in the underlying data store and make any necessary changes to schemas, if your data store requires that.

Duende.IdentityServer.EntityFramework

We also provide a default implementation of the stores in the Duende.IdentityServer.EntityFramework package, but this implementation is still highly abstracted because it is usable with any database that has an EF provider. Different database vendors have very different dialects of sql that have different syntax and type systems, so we don’t provide schema changes directly. Instead, we provide the Entity Framework entities and mappings which can be used with Entity Framework’s migrations feature to generate the schema updates that are needed in your database.

To generate a migration for the new columns, run the command below. Note that you might need to adjust paths based on your specific organization of the migration files.

dotnet ef migrations add Update_DuendeIdentityServer_v7_0 -c ConfigurationDbContext -o Migrations/ConfigurationDb

dotnet ef migrations add Update_DuendeIdentityServer_v7_0 -c PersistedGrantDbContext -o Migrations/PersistedGrantDb

The ServerSideSession.Id property has been changed from int to long. The generated migration handles the data type change but does not take into account that this column is the primary key for the table. If the primary key constraint is not removed, running the migration will fail. The primary key of the table needs to be dropped before the type is altered and then recreated. The easiest way to do so is to update the generated migration file:

// Add this line before AlterColumn
migrationBuilder.DropPrimaryKey("PK_ServerSideSessions", "ServerSideSessions");

// This should already be in the generated code
migrationBuilder.AlterColumn<long>(
    name: "Id",
    table: "ServerSideSessions",
    type: "bigint",
    nullable: false,
    oldClrType: typeof(int),
    oldType: "int")
    .Annotation("SqlServer:Identity", "1, 1")
    .OldAnnotation("SqlServer:Identity", "1, 1");

// Add this after AlterColumn
migrationBuilder.AddPrimaryKey("PK_ServerSideSessions", "ServerSideSessions", "Id");

For most deployments the approach above will work. For deployments with high volumes of sessions, high availability demands and no low traffic hours this might cause unacceptable downtime. The impact and possible workarounds depend on the database engine used, but most databases include features for performing indexing operations and schema changes to an online system. Please consult your database’s documentation or your DBA for more guidance.

Once you’ve updated that migration, both migrations should be applied to your database:

dotnet ef database update -c ConfigurationDbContext

dotnet ef database update -c PersistedGrantDbContext

Some organizations prefer to use other tools for managing schema changes. You’re free to manage your schema however you see fit, as long as the entities can be successfully mapped. Even if you’re not going to ultimately use Entity Framework migrations to manage your database changes, generating a migration can be a useful development step to get an idea of what needs to be done.

Step 4: Breaking changes

Only impacts particular customizations or edge cases

  • The DefaultCorsPolicyService now depends on the IConfigurationDbContext directly, instead of taking a dependency on the IServiceProvider and resolving that DbContext from it. If you have a customized CORS implementation that derives from the DefaultCorsPolicyService, you need to update the constructor of your derived class to use the IConfigurationDbContext.
  • The DPoPProofValidatonContext has been refactored. Instead of the Client property, we now put the relevant details (expiration validation mode and clock skew) directly in the context. We also have added the HTTP method and URL to the context. If you have a custom implementation of the IDPoPProofValidator or a class that derives from the DefaultDPoPProofValidator, update your usage of the context appropriately.
  • The DefaultTokenService no longer includes an IHttpContextAccessor. This member was unused by the default implementation and marked as obsolete. Customizations that derive from the DefaultTokenService no longer need to pass the accessor to the base constructor. If such a customization needs the accessor, add it to the derived class.
  • The ValidatedAuthorizeRequest.RequestedResourceIndiators property was misspelled and has been renamed RequestedResourceIndicators.
  • The reference token store now includes the session id when revoking reference tokens. Implementors of IReferenceTokenStore should update their implementation of token revocation to include the session id.
  • Invalid prompt modes now cause validation errors that result in an HTTP 400 (Bad Request). Previously, invalid prompt modes were ignored. This complies with updates to the OpenID Connect specification.
  • A new interface IIdentityServerTools has been introduced for the IdentityServerTools helper class to allow mocking. Update any direct references to IdentityServerTools to IIdentityServerTools.

Newly Deprecated

  • IAuthorizationParametersMessageStore is deprecated. PAR is a more robust/standardized approach to get similar benefits.

  • The IHttpContextAccessor in the EndSessionRequestValidator is unused and has been marked as obsolete. It will be removed in a future version.

Previously Deprecated, Now Removed

  • The obsolete IdentityServerOrigin constant has been removed.
  • Several obsolete extension methods on HttpContext have been removed. These methods are replaced by methods in IServerUrls and IIssuerNameService. See #1457
    • HttpContext.GetSchemeSupportsSignOutAsync is replaced by IAuthenticationHandlerProvider.GetHandlerAsync (you will also need to check if the handler implements IAuthenticationSignOutHandler).
    • HttpContext.GetIdentityServerOrigin and HttpContext.SetIdentityServerOrigin are replaced by IServerUrls.Origin.
    • HttpContext.GetIdentityServerBasePath and HttpContext.SetIdentityServerBasePath are replaced by IServerUrls.BasePath.
    • GetIdentityServerHost is replaced by IServerUrls.Origin
    • GetIdentityServerBaseUrl is replaced by IServerUrls.BaseUrl
    • GetIdentityServerRelativeUrl is replaced by IServerUrls.GetIdentityServerRelativeUrl
    • GetIdentityServerIssuerUri is replaced by IIssuerNameService.GetCurrentAsync
    • RedirectToAbsoluteUrl is replaced by redirecting to a call to IServerUrls.GetAbsoluteUrl.
  • The obsolete and unused IUserSessionExtensions interface has been removed.
  • The obsolete IPrincipal.GetName and IIdentity.GetName extension methods have been removed. Use ClaimsPrincipal.GetDisplayName instead.
  • The obsolete ResourceValidationRequest.IncludeNonIsolatedApiResources has been removed. This flag was no longer used.

Unlikely to impact anyone

  • The KeyManagementOptions.SigningAlgorithms is now an ICollection rather than an IEnumerable. If you are configuring signing algorithms using code, and setting the SigningAlgorithms to some type that implements IEnumerable but not ICollection, then you must change the type that you are using. In practice, we expect everyone uses a list or array (which are both ICollections).

  • The value of the constant IdentityServerAuthenticationType has changed from “IdentityServer4” to “Duende.IdentityServer”. This constant is used as the value of the authentication type within the ClaimsIdentity that IdentityServer constructs. The authentication type’s value is never used by IdentityServer or ASP.NET, so this is unlikely to impact anyone. It is also the name of the default cors policy created by IdentityServer. This could theoretically impact you if you have a CORS policy named “Duende.IdentityServer”, as the new name now conflicts.

Step 5: Done!

That’s it. Of course, at this point you can and should test that your IdentityServer is updated and working properly.

Step 6 (if needed): Troubleshooting .NET 8 Issues

You are now done with everything related to the IdentityServer upgrade, but we would also like to add a references to breaking changes in .NET 8 and associated libraries that are likely to affect the IdentityServer setup.

  • Entity Framework Core 8 has some performance improvements in the query generation for Microsoft SQL Server that relies on database features that requires a database compatibility level of at least 130. This can cause errors on database access like Microsoft.Data.SqlClient.SqlException (0x80131904): The syntax near '$' is incorrect. or Microsoft.Data.SqlClient.SqlException (0x80131904): Incorrect syntax near the keyword 'WITH'. Please see https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-8.0/breaking-changes#mitigations for more information.
  • For container deployments, the default ASP.NET Core port changed from 80 to 8080. This might require configuration updates to either continue using port 80 or to migrate to using 8080.
  • IdentityServer and the Asp.Net Authentication packages all depends on the Microsoft.IdentityModel.* packages. With packages that are brought in as transient dependencies there is less control of the versions being pulled in. If different packages from the Microsoft.IdentityModel.* family end up having different versions, there will be odd bugs. We’ve seen reports where the refresh token isn’t stored and where the OIDC handler fails to redirect to an OIDC Provider because it failed reading the discovyer document. Always ensure that all Microsoft.IdentityModel.* packages are of exactly the same version. If they are not, you might need to make an explicit <PackageReference> to pin an exact version.