Migrate to OpenIddict 7.0
What's new?
The most important changes introduced in 7.0 can be found here.
IMPORTANT
Migrating to OpenIddict 7.0 requires making changes to your database as an existing property has been updated to allow larger values.
Update your packages references
For that, update your .csproj
file to reference the OpenIddict
7.x packages. For instance:
<ItemGroup>
<!-- OpenIddict 6.x: -->
<PackageReference Include="OpenIddict.AspNetCore" Version="6.4.0" />
<PackageReference Include="OpenIddict.EntityFrameworkCore" Version="6.4.0" />
<!-- OpenIddict 7.x: -->
<PackageReference Include="OpenIddict.AspNetCore" Version="7.0.0" />
<PackageReference Include="OpenIddict.EntityFrameworkCore" Version="7.0.0" />
</ItemGroup>
NOTE
Migrating to ASP.NET Core 9.0 is not required, as OpenIddict 7.0 is still natively compatible with ASP.NET Core 2.3 (.NET Framework-only) 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.3 | .NET Framework 4.6.2 |
ASP.NET Core 2.3 | .NET Framework 4.7.2 |
ASP.NET Core 2.3 | .NET Framework 4.8 |
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 6.0 TFM has been removed as this version is no longer supported by Microsoft.
While most OpenIddict 7.0 packages can still be used on this version 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 or .NET 9.0.
If applicable, add and apply migrations
If your application uses Entity Framework Core or Entity Framework 6, add a migration to react to the schema change listed below and apply it.
Updated properties
Table | Column name | Maximum length in 6.x | Maximum length in 7.x |
---|---|---|---|
OpenIddictTokens | Type | 50 | 150 |
If applicable, migrate from ASP.NET Core 2.1 to ASP.NET Core 2.3
Following the release of ASP.NET Core 2.3 in February 2025, the ASP.NET Core integrations for the OpenIddict client, server and validation now explicitly target ASP.NET Core 2.3 instead of 2.1 for the .NET Standard 2.0+ and .NET Framework 4.6.2+ target framework monikers. Going forward, it is expected that security issues affecting ASP.NET Core 2.1/2.3 on .NET Framework will be exclusively fixed via 2.3.x releases, so it is essential that all "ASP.NET Core on .NET Framework" users move to 2.3 as soon as possible.
CAUTION
While 2.3 is a "minor" release according to the SemVer versioning scheme, it's actually a source and binary-breaking update since none of the APIs present in 2.2 are available in 2.3. OpenIddict itself is not affected (since previous versions always targeted 2.1 and didn't use any of the APIs present in 2.2) but it may break third-party libraries or custom application code: make sure to test your application after migrating from ASP.NET Core 2.1 to 2.3.
If applicable, migrate from Entity Framework Core 2.1 to Entity Framework Core 2.3
As part of the ASP.NET Core 2.1 -> ASP.NET Core 2.3 migration, Entity Framework Core 2.1 was also re-released as Entity Framework Core 2.3. To ensure EF Core users using the OpenIddict.EntityFrameworkCore
package on .NET Framework 4.6.2+ always reference a supported version, OpenIddict.EntityFrameworkCore
was updated to require 2.3 as the minimal version: as such, make sure your application only references Entity Framework Core 2.3 packages and not the now-obsolete 2.1 version.
If applicable, migrate from .NET Extensions 2.1 to .NET Extensions 8.0
While ASP.NET Core 2.1 exclusively referenced the 2.1.x versions of the Microsoft.Extensions.*
packages, ASP.NET Core 2.3 and Entity Framework 2.3 now reference the 8.0 version. As such, OpenIddict 7.0 was updated to reference the 8.0 version of the Microsoft.Extensions.*
packages for the .NET Standard 2.0+, .NET Framework 4.6.2+ and .NET 8.0 target framework monikers.
TIP
As part of this change, APIs that used to be .NET 8.0+-only in OpenIddict - like TimeProvider
or JsonNode
- are now supported on all platforms.
For more information, see https://github.com/openiddict/openiddict-core/releases/tag/7.0.0-preview.1.
IMPORTANT
When migrating a .NET Framework application from .NET Extensions 2.1 to .NET Extensions 8.0, you'll likely need to regenerate your binding redirects: make sure to review them carefully to ensure they all point to the 8.0 version of the .NET Extensions.
If applicable, update third-party authorization servers and client applications when using client assertions
OpenIddict 7.0 proactively implements the Updates to Audience Values for OAuth 2.0 Authorization Servers draft: while it hasn't been officially adopted yet, it fixes a vulnerability affecting the standard private_jwt_key
client authentication method. To address the issue, this specification introduces important breaking changes in multiple OAuth 2.0 and OpenID Connect specifications, which affects the following scenarios (that are no longer supported in OpenIddict 7.0 for security reasons):
A client application authenticates with an authorization server that requires the use of the
token_endpoint
as the audience of client assertions, even for endpoints other than the token endpoint (e.g., the "pushed authorization endpoint" or the "introspection endpoint").A client application authenticates with an authorization server that does not support the new
client-authentication+jwt
JSON Web Token type defined by the specification.An authorization server allows client applications to authenticate with client assertions that use
token_endpoint
instead ofissuer
- which is now the only allowed value - as the audience.An authorization server allows client applications to authenticate with client assertions that do not use the new
client-authentication+jwt
JSON Web Token type.
TIP
The OpenIddict client, server and validation stacks have all been updated to support the new requirements introduced by this specification and it is expected that other implementations will make similar changes in the near future: make sure to update these applications when migrating to OpenIddict 7.0 to avoid any interruption.
For environments that exclusively use the OpenIddict client and server stacks, make sure to migrate all applications to 7.0 simultaneously.
Update your authorization controller to use TempData
to detect login loops
OpenIddict 7.0 no longer allows dynamically overriding the prompt
value when using OAuth 2.0 Pushed Authorization Requests as the logic internally used to support this scenario was too fragile and was removed. To prevent login endpoint -> authorization endpoint
loops, developers are now encouraged use TempData
to store a flag indicating whether the user has already been offered to re-authenticate and avoid triggering a new authentication challenge in that case. Here's an example:
[HttpGet("~/connect/authorize")]
[HttpPost("~/connect/authorize")]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> Authorize()
{
// Note: the request object contains all the parameters specified in the query string or request form
// or initially sent to the pushed authorization endpoint for a PAR-enabled authorization flow.
// As such, the data contained in this object MUST NOT be serialized or returned unprotected to the
// user agent (e.g as HTML hidden input fields). If only the query string or request form parameters
// need to be resolved, the Request.Query and Request.Form collections must be used instead.
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
// Try to retrieve the user principal stored in the authentication cookie and redirect
// the user agent to the login page (or to an external provider) in the following cases:
//
// - If the user principal can't be extracted or the cookie is too old.
// - If prompt=login was specified by the client application.
// - If max_age=0 was specified by the client application (max_age=0 is equivalent to prompt=login).
// - If a max_age parameter was provided and the authentication cookie is not considered "fresh" enough.
//
// For scenarios where the default authentication handler configured in the ASP.NET Core
// authentication options shouldn't be used, a specific scheme can be specified here.
var result = await HttpContext.AuthenticateAsync();
if (result is not { Succeeded: true } ||
((request.HasPromptValue(PromptValues.Login) || request.MaxAge is 0 ||
(request.MaxAge != null && result.Properties?.IssuedUtc != null &&
TimeProvider.System.GetUtcNow() - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value))) &&
TempData["IgnoreAuthenticationChallenge"] is null or false))
{
// If the client application requested promptless authentication,
// return an error indicating that the user is not logged in.
if (request.HasPromptValue(PromptValues.None))
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is not logged in."
}));
}
// To avoid endless login endpoint -> authorization endpoint redirects, a special temp data entry is
// used to skip the challenge if the user agent has already been redirected to the login endpoint.
//
// Note: this flag doesn't guarantee that the user has accepted to re-authenticate. If such a guarantee
// is needed, the existing authentication cookie MUST be deleted AND revoked (e.g using ASP.NET Core
// Identity's security stamp feature with an extremely short revalidation time span) before triggering
// a challenge to redirect the user agent to the login endpoint.
TempData["IgnoreAuthenticationChallenge"] = true;
// For scenarios where the default challenge handler configured in the ASP.NET Core
// authentication options shouldn't be used, a specific scheme can be specified here.
return Challenge(new AuthenticationProperties
{
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(
Request.HasFormContentType ? Request.Form : Request.Query)
});
}
// ...
}
If applicable, replace references to IOpenIddict*StoreResolver
by IOpenIddict*Store<T>
As part of an extensive effort aiming at making OpenIddict 7.0 compatible with trimming and Native AOT, the 4 IOpenIddict*StoreResolver
interfaces and their implementations have been removed in 7.0. If you use custom implementations of the application, authorization, scope or token managers, make sure to update the constructor to directly use IOpenIddict*Store<T>
instead of IOpenIddict*StoreResolver
. E.g:
public sealed class CustomTokenManager<TToken> : OpenIddictTokenManager<TToken> where TToken : class
{
public CustomTokenManager(
IOpenIddictTokenCache<TToken> cache,
ILogger<OpenIddictTokenManager<TToken>> logger,
IOptionsMonitor<OpenIddictCoreOptions> options,
IOpenIddictTokenStore<TToken> store)
: base(cache, logger, options, store)
{
}
}
TIP
The managers/stores service registration APIs present in OpenIddictCoreBuilder
have also been reworked to make them trimming and Native AOT-compatible, which may require making a few changes to your code.
See https://github.com/openiddict/openiddict-core/releases/tag/7.0.0-preview.2 for more information
If applicable, update your custom EF Core stores to use the new IOpenIddictEntityFrameworkContext
and IOpenIddictEntityFrameworkCoreContext
interface
In OpenIddict 7.0, the OpenIddictEntityFramework*Store<...>
and OpenIddictEntityFrameworkCore*Store<...>
classes have all been updated to avoid taking DbContext
as a generic argument in their type definition (which was a pattern inspired by ASP.NET Core Identity).
The DbContext
used by the OpenIddict stores is now resolved via dedicated IOpenIddictEntityFrameworkContext
and IOpenIddictEntityFrameworkCoreContext
services and a default implementation is automatically registered when calling options.UseEntityFrameworkCore().UseDbContext<TContext>()
.
When using custom stores, make sure to remove the TContext
argument from the type definition and to replace TContext
by IOpenIddictEntityFrameworkContext
or IOpenIddictEntityFrameworkCoreContext
in the constructor parameters:
public sealed class CustomAuthorizationStore : OpenIddictEntityFrameworkCoreAuthorizationStore<
CustomAuthorization,
CustomApplication,
CustomToken,
string>
{
public CustomAuthorizationStore(
IMemoryCache cache,
IOpenIddictEntityFrameworkCoreContext context,
IOptionsMonitor<OpenIddictEntityFrameworkCoreOptions> options)
: base(cache, context, options)
{
}
}
If applicable, replace the token_type_hint
-inspired token types by the new URI-style token type identifiers
As part of the OAuth 2.0 Token Exchange introduction, the OpenIddict client, server and validation stacks have been updated to use the new URI-style token type identifiers to represent token types. While internally massive, most OpenIddict users won't notice this change, as the OpenIddictTokenManager
class was updated to map legacy token types stored in the tokens table to their new equivalent.
Only advanced OpenIddict users using the ClaimsPrincipal.GetTokenType()
/ClaimsPrincipal.SetTokenType()
extensions or implementing custom handlers for the ValidateToken
/GenerateToken
events will need to update their code to use the appropriate token type identifier:
public static class OpenIddictConstants
{
public static class TokenTypeIdentifiers
{
public const string AccessToken = "urn:ietf:params:oauth:token-type:access_token";
public const string GenericJsonWebToken = "urn:ietf:params:oauth:token-type:jwt";
public const string GenericSaml1Assertion = "urn:ietf:params:oauth:token-type:saml1";
public const string GenericSaml2Assertion = "urn:ietf:params:oauth:token-type:saml2";
public const string IdentityToken = "urn:ietf:params:oauth:token-type:id_token";
public const string RefreshToken = "urn:ietf:params:oauth:token-type:refresh_token";
public static class Private
{
public const string AuthorizationCode = "urn:openiddict:params:oauth:token-type:authorization_code";
public const string ClientAssertion = "urn:openiddict:params:oauth:token-type:client_assertion";
public const string DeviceCode = "urn:openiddict:params:oauth:token-type:device_code";
public const string RequestToken = "urn:openiddict:params:oauth:token-type:request_token";
public const string StateToken = "urn:openiddict:params:oauth:token-type:state_token";
public const string UserCode = "urn:openiddict:params:oauth:token-type:user_code";
public const string UserInfoToken = "urn:openiddict:params:oauth:token-type:userinfo_token";
}
}
}
If applicable, register audiences and resources
As part of the OAuth 2.0 Token Exchange support, the OpenIddict server now automatically validates the standard audience
and resource
parameters used in token exchange requests and the resource
parameters present in authorization and pushed authorization requests. To ensure OpenIddict doesn't reject requests that use these parameters, the allowed audiences and resources must now be registered in the server options:
services.AddOpenIddict()
.AddServer(options =>
{
options.RegisterAudiences("financial_api");
options.RegisterResources("https://fabrikam.com/financial_api");
});
TIP
Alternatively, audiences and resources validation can be disabled, which can be useful when implementing dynamic audiences/resources:
services.AddOpenIddict()
.AddServer(options =>
{
options.DisableAudienceValidation();
options.DisableResourceValidation();
});
Since explicit permissions must also be granted for each client application using them, make sure to add the appropriate permissions when creating (or updating) the client applications using OpenIddictApplicationManager<TApplication>
:
var descriptor = new OpenIddictApplicationDescriptor
{
// ...
};
descriptor.AddAudiencePermissions("financial_api");
descriptor.AddResourcePermissions("https://fabrikam.com/financial_api");
await manager.CreateAsync(descriptor);
TIP
If necessary, both audience and resource permissions can be ignored, which can be useful if custom validation
- e.g implemented directly in the authorization controller, to > support dynamic values - is preferred.
services.AddOpenIddict()
.AddServer(options =>
{
options.IgnoreAudiencePermissions();
options.IgnoreResourcePermissions();
});
If applicable, react to the breaking changes introduced in OpenIddictParameter
Multiple enhancements have been introduced in the OpenIddictParameter
structure to guarantee its immutability.
While most users shouldn't be affected, read https://github.com/openiddict/openiddict-core/releases/tag/7.0.0-preview.1 if you're seeing compilation errors after migrating to OpenIddict 7.0.
If applicable, update your code to react to the change affecting the Discord provider
In OpenIddict 7.0, the Discord provider was updated to use the /users/@me
endpoint instead of /oauth2/@me
, which improves how userinfo claims are represented and returned to the authentication controller. This behavior change is breaking so developers are encouraged to review their Discord integration to determine whether their code should be updated to support the new claims representation.
TIP
See https://discord.com/developers/docs/resources/user#user-object for more information on the claims returned by the /users/@me
endpoint.