Migrate to OpenIddict 6.0
What's new?
The most important changes introduced in 6.0 can be found here.
NOTE
Migrating to OpenIddict 6.0 doesn't require making changes to your database but new APIs have been added to the stores and existing APIs have been reworked: if you're using custom stores, you'll need to tweak them when upgrading to OpenIddict 6.0.
Update your packages references
For that, update your .csproj
file to reference the OpenIddict
6.x packages. For instance:
<ItemGroup>
<!-- OpenIddict 5.x: -->
<PackageReference Include="OpenIddict.AspNetCore" Version="6.0.0" />
<PackageReference Include="OpenIddict.EntityFrameworkCore" Version="6.0.0" />
<!-- OpenIddict 6.x: -->
<PackageReference Include="OpenIddict.AspNetCore" Version="6.0.0-rc1.24608.69" />
<PackageReference Include="OpenIddict.EntityFrameworkCore" Version="6.0.0-rc1.24608.69" />
</ItemGroup>
NOTE
Migrating to ASP.NET Core 9.0 is not required, as OpenIddict 6.0 is still natively compatible with ASP.NET Core 2.1 (.NET Framework-only), ASP.NET Core 6.0 and ASP.NET Core 8.0. Moving to a newer .NET runtime or ASP.NET Core can (and even should!) be done separately for a simpler/decoupled upgrade:
Web framework version | .NET runtime version |
---|---|
ASP.NET Core 2.1 | .NET Framework 4.6.2 |
ASP.NET Core 2.1 | .NET Framework 4.7.2 |
ASP.NET Core 2.1 | .NET Framework 4.8 |
ASP.NET Core 6.0 | .NET 6.0 |
ASP.NET Core 8.0 | .NET 8.0 |
ASP.NET Core 9.0 | .NET 9.0 |
Microsoft.Owin 4.2 | .NET Framework 4.6.2 |
Microsoft.Owin 4.2 | .NET Framework 4.7.2 |
Microsoft.Owin 4.2 | .NET Framework 4.8 |
IMPORTANT
The .NET 7.0 and .NET Framework 4.6.1 TFMs have been removed as these versions are no longer supported by Microsoft.
While most OpenIddict 6.0 packages can still be used on these versions thanks to their .NET Standard 2.0 or 2.1 TFMs, doing that is strongly discouraged and users are instead encouraged to migrate to .NET 8.0 and .NET Framework 4.6.2 (or higher).
React to the naming changes impacting some of the server endpoints
Some of the server endpoints have been renamed in OpenIddict 6.0 to be more specific or more closely match the official names, which should reduce ambiguities and make migrating from other OAuth 2.0/OIDC stacks to OpenIddict easier:
OpenIddict 5.x | OpenIddict 6.x |
---|---|
Cryptography endpoint | JSON Web Key Set endpoint |
Device endpoint | Device authorization endpoint |
Logout endpoint | End-session endpoint |
Userinfo endpoint | UserInfo endpoint |
Verification endpoint | End-user verification endpoint |
As such, all the constants, builder methods, events and event handlers used by the OpenIddict client, core, server and validation stacks have been updated to use the new names. In most cases, reacting to this breaking change should be limited to just changing a few lines in your Startup
file:
OpenIddict 5.x | OpenIddict 6.x |
---|---|
options.SetCryptographyEndpointUris() | options.SetJsonWebKeySetEndpointUris() |
options.SetDeviceEndpointUris() | options.SetDeviceAuthorizationEndpointUris() |
options.SetLogoutEndpointUris() | options.SetEndSessionEndpointUris() |
options.SetUserinfoEndpointUris() | options.SetUserInfoEndpointUris() |
options.SetVerificationEndpointUris() | options.SetEndUserVerificationEndpointUris() |
OpenIddict 5.x | OpenIddict 6.x |
---|---|
options.AllowDeviceCodeFlow() | options.AllowDeviceAuthorizationFlow() |
OpenIddict 5.x | OpenIddict 6.x |
---|---|
options.EnableLogoutEndpointPassthrough() | options.EnableEndSessionEndpointPassthrough() |
options.EnableUserinfoEndpointPassthrough() | options.EnableUserInfoEndpointPassthrough() |
options.EnableVerificationEndpointPassthrough() | options.EnableEndUserVerificationEndpointPassthrough() |
OpenIddict 5.x | OpenIddict 6.x |
---|---|
OpenIddictConstants.Permissions.Endpoints.Device | OpenIddictConstants.Permissions.Endpoints.DeviceAuthorization |
OpenIddictConstants.Permissions.Endpoints.Logout | OpenIddictConstants.Permissions.Endpoints.EndSession |
TIP
While not mandatory (as the permissions containing the old endpoint names are still fully functional in 6.x for backward compatibility), you can also update your applications table/database to use the new constant values (i.e ept:device_authorization
and ept:end_session
instead of ept:device
and ept:logout
).
React to the changes affecting prompt values in authorization requests
Complete support for the the Initiating User Registration via OpenID Connect specification has been introduced in OpenIddict 6.0, that will now automatically validate the prompt
parameter to ensure the value is supported and return the supported values in the server configuration document using the new standard prompt_values_supported
node (see https://github.com/openiddict/openiddict-core/pull/2197 for more information).
If you're using custom prompt values (i.e values different from consent
, login
, select_account
and none
), you'll need to update your server configuration to ensure these custom values are not rejected by OpenIddict when validating authorization requests:
services.AddOpenIddict()
.AddServer(options =>
{
// ...
options.RegisterPromptValues("custom_value_1", "custom_value_2");
});
As part of this change, the OpenIddictConstants.Prompts
class have been renamed to OpenIddictConstants.PromptValues
and the OpenIddictRequest.GetPrompts()
/OpenIddictRequest.HasPrompt()
extensions have been renamed to OpenIddictRequest.GetPromptValues()
and OpenIddictRequest.HasPromptValue()
to match the names used in this specification. If you're using these APIs, make sure you're updating the corresponding calls when migrating to OpenIddict 6.0.
React to the claim issuer changes in the client stack
Starting with 6.0, OpenIddict now allows customizing the claims issuer used to populate the Claim.Issuer
and Claim.OriginalIssuer
properties (this option is specially useful when using the OpenIddict client in legacy ASP.NET 4.6.2+ applications using ASP.NET Identity, since the Claim.Issuer
property is directly reflected in the user interface):
options.AddRegistration(new OpenIddictClientRegistration
{
// ...
Issuer = new Uri("https://localhost:44395/", UriKind.Absolute),
ClaimsIssuer = "Local authorization server"
});
options.UseWebProviders()
.AddActiveDirectoryFederationServices(options =>
{
// ...
options.SetClaimsIssuer("Contoso");
});
As part of this change, the OpenIddict client now uses OpenIddictClientRegistration.ProviderName
instead of the issuer URI as the first fallback value when OpenIddictClientRegistration.ClaimsIssuer
is not explicitly set, which is consistent with the pattern used in the OAuth 2.0-based social providers developed by Microsoft and the community (if no provider name was set, the issuer URI is used as the claims issuer, as in previous versions).
If your code relies on a specific Claim.Issuer
or Claim.OriginalIssuer
value, you'll need to either update it to match the new logic or set ClaimsIssuer
(or call options.SetClaimsIssuer()
for a web provider) so that the registration uses the issuer URI as the claims issuer:
options.AddRegistration(new OpenIddictClientRegistration
{
// ...
Issuer = new Uri("https://localhost:44395/", UriKind.Absolute),
ClaimsIssuer = "https://localhost:44395/"
});
options.UseWebProviders()
.AddFacebook(options =>
{
// ...
options.SetClaimsIssuer("https://www.facebook.com/");
});
TIP
The complete list of providers with their issuer URIs can be found here: https://github.com/openiddict/openiddict-core/blob/dev/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml
If applicable, migrate to MongoDB.Driver
version 3.0
To fix a breaking change introduced by the MongoDB in their 2.x branch, the OpenIddict.MongoDb
and OpenIddict.MongoDb.Models
packages now reference MongoDB.Driver
and MongoDB.Bson
version 3.0.0 and are now strong-named starting with OpenIddict 6.0: OpenIddict users relying on the MongoDB integration will also need to update their MongoDB driver dependencies when upgrading OpenIddict.
IMPORTANT
The third iteration of the C# MongoDB driver no longer supports .NET Standard 2.0 and requires .NET Framework 4.7.2 as the minimum version: projects referencing the OpenIddict.MongoDb
or OpenIddict.MongoDb.Models
packages and targeting .NET Standard 2.0 or .NET Framework < 4.7.2 will have to be updated when migrating to OpenIddict 6.0.
If applicable, update your checks to ensure authenticated identities are correctly identified (OWIN only)
In OpenIddict 6.0, the ASP.NET Core and OWIN integrations now include the authentication properties attached to ProcessAuthenticationContext.Properties
in errored authentication results, which is useful when used with the client stack as it allows retrieving custom and non-custom properties attached to the state
token when using the "error pass-through mode".
As part of this change, the OWIN hosts now return an AuthenticateResult
instance containing an empty ClaimsIdentity
with its IsAuthenticated
property set to false
(instead of a null
identity) to represent errored authentication demands.
If you're using the error pass-through mode and are calling await AuthenticateAsync(OpenIddict*OwinDefaults.AuthenticationType)
, make sure you're updating your if
checks to ensure authenticated identities are correctly identified:
var result = await context.Authentication.AuthenticateAsync(OpenIddictClientOwinDefaults.AuthenticationType);
if (result is { Identity.IsAuthenticated: true })
{
// The authentication result represents an authenticated user.
}
If applicable, update your custom stores
New APIs
New authorization and token stores APIs have been introduced in OpenIddict 6.0 to make revocation faster:
/// <summary>
/// Revokes all the authorizations matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the authorization, or <see langword="null"/> not to filter out specific subjects.</param>
/// <param name="client">The client associated with the authorization, or <see langword="null"/> not to filter out specific clients.</param>
/// <param name="status">The authorization status, or <see langword="null"/> not to filter out specific authorization statuses.</param>
/// <param name="type">The authorization type, or <see langword="null"/> not to filter out specific authorization types.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The number of authorizations corresponding to the criteria that were marked as revoked.</returns>
ValueTask<long> RevokeAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken);
/// <summary>
/// Revokes all the authorizations associated with the specified application identifier.
/// </summary>
/// <param name="identifier">The application identifier associated with the authorizations.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The number of authorizations associated with the specified application that were marked as revoked.</returns>
ValueTask<long> RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken);
/// <summary>
/// Revokes all the authorizations associated with the specified subject.
/// </summary>
/// <param name="subject">The subject associated with the authorizations.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The number of authorizations associated with the specified subject that were marked as revoked.</returns>
ValueTask<long> RevokeBySubjectAsync(string subject, CancellationToken cancellationToken);
/// <summary>
/// Revokes all the tokens matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the token, or <see langword="null"/> not to filter out specific subjects.</param>
/// <param name="client">The client associated with the token, or <see langword="null"/> not to filter out specific clients.</param>
/// <param name="status">The token status, or <see langword="null"/> not to filter out specific token statuses.</param>
/// <param name="type">The token type, or <see langword="null"/> not to filter out specific token types.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The number of tokens corresponding to the criteria that were marked as revoked.</returns>
ValueTask<long> RevokeAsync(string? subject, string? client, string? status, string? type, CancellationToken cancellationToken);
/// <summary>
/// Revokes all the tokens associated with the specified application identifier.
/// </summary>
/// <param name="identifier">The application identifier associated with the tokens.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The number of tokens associated with the specified application that were marked as revoked.</returns>
ValueTask<long> RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken = default);
/// <summary>
/// Revokes all the tokens associated with the specified authorization identifier.
/// </summary>
/// <param name="identifier">The authorization identifier associated with the tokens.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The number of tokens associated with the specified authorization that were marked as revoked.</returns>
ValueTask<long> RevokeByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken);
/// <summary>
/// Revokes all the tokens associated with the specified subject.
/// </summary>
/// <param name="subject">The subject associated with the tokens.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The number of tokens associated with the specified subject that were marked as revoked.</returns>
ValueTask<long> RevokeBySubjectAsync(string subject, CancellationToken cancellationToken = default);
Updated and removed APIs
All the existing FindAsync()
overloads present in the authorization and token stores have been merged in OpenIddict 6.0 into a single overload to make them more flexible. All the parameters are now optional:
/// <summary>
/// Retrieves the authorizations matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the authorization, or <see langword="null"/> not to filter out specific subjects.</param>
/// <param name="client">The client associated with the authorization, or <see langword="null"/> not to filter out specific clients.</param>
/// <param name="status">The authorization status, or <see langword="null"/> not to filter out specific authorization statuses.</param>
/// <param name="type">The authorization type, or <see langword="null"/> not to filter out specific authorization types.</param>
/// <param name="scopes">The minimal scopes associated with the authorization, or <see langword="null"/> not to filter out scopes.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The authorizations corresponding to the criteria.</returns>
IAsyncEnumerable<TAuthorization> FindAsync(
string? subject, string? client,
string? status, string? type,
ImmutableArray<string>? scopes, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the tokens matching the specified parameters.
/// </summary>
/// <param name="subject">The subject associated with the token, or <see langword="null"/> not to filter out specific subjects.</param>
/// <param name="client">The client associated with the token, or <see langword="null"/> not to filter out specific clients.</param>
/// <param name="status">The token status, or <see langword="null"/> not to filter out specific token statuses.</param>
/// <param name="type">The token type, or <see langword="null"/> not to filter out specific token types.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The tokens corresponding to the criteria.</returns>
IAsyncEnumerable<TToken> FindAsync(
string? subject, string? client,
string? status, string? type, CancellationToken cancellationToken);