Skip to content

7.0.0-preview.2

Pre-release
Pre-release
Compare
Choose a tag to compare
@kevinchalet kevinchalet released this 15 Apr 14:10
524ce1f

This release introduces the following changes:

  • All the OpenIddict assemblies have been marked as trimming and Native AOT-compatible (only on .NET 9.0 and higher). For that, several changes had to be made to the OpenIddict core stack:

    • The store resolver interfaces (IOpenIddict*StoreResolver) and all their implementations have been removed and the managers have been updated to now directly take an IOpenIddict*Store<T> argument instead of an IOpenIddict*StoreResolver.

    • All the OpenIddictCoreOptions.Default*Type options (e.g DefaultApplicationType) have been removed and the untyped managers (IOpenIddict*Manager) no longer use options to determine the actual entity type at runtime. Instead, each store integration is now responsible for replacing the IOpenIddict*Manager services with a service descriptor pointing to the generic OpenIddict*Manager<T> implementation with the correct T argument: by default, the default entity types provided by the store are used, but the managers can be re-registered with a different type when the user decides to use different models (e.g via options.UseEntityFrameworkCore().ReplaceDefaultModels<...>()).

    • All the managers/store/store resolvers registration APIs offered by OpenIddictCoreBuilder have been removed: while they were very powerful and easy-to-use (e.g the Replace*Manager methods supported both open and closed generic types and were able to determine the entity type from the base type definition), they weren't AOT-compatible.

    • New AOT-friendly Replace*Store() and Replace*Manager() APIs have been introduced in OpenIddictCoreBuilder. The new Replace*Manager() APIs have two overloads that can be used depending on whether you need to register a closed or open generic type:

      options.ReplaceApplicationManager<
          /* TApplication: */ OpenIddictEntityFrameworkCoreApplication,
          /* TManager: */ CustomApplicationManager<OpenIddictEntityFrameworkCoreApplication>>();
      options.ReplaceApplicationManager(typeof(CustomApplicationManager<>));
    • While they are currently not functional on Native AOT due to EF Core not supporting interpreted LINQ expressions yet, the EF Core stores package has been updated to be ready for AOT: as part of this change, the signature of all the stores has been updated to remove the TContext generic argument from the definition. Similarly, the MongoDB C# driver isn't AOT (or even trimming) compatible yet, but the stores have been updated to ensure they only use statically-analyzable patterns.

    • A new IOpenIddictEntityFrameworkCoreContext interface containing a single ValueTask<DbContext> GetDbContextAsync(CancellationToken cancellationToken) method (similar to what's currently used in the MongoDB integration) has been introduced to allow each to resolve the DbContext to use. A default implementation named OpenIddictEntityFrameworkCoreContext<TContext> is used by the OpenIddictEntityFrameworkCoreBuilder.UseDbContext<TContext>() API to resolve the TContext type specified by the user.

    • The OpenIddictEntityFrameworkCoreBuilder.ReplaceDefaultEntities<...> API has been preserved - including the overload accepting a single TKey parameter but no longer use options internally. Instead, they re-register the untyped IOpenIddict*Manager to point to the correct OpenIddict*Manager<T> instances depending on the generic types set by the user.

  • For consistency with the Entity Framework Core stores, the OpenIddictEntityFrameworkBuilder.UseDbContext<TContext>() API will no longer automatically register the DbContext type in the DI container.

  • The authorization endpoint now uses Cache-Control: no-store instead of Cache-Control: no-cache when generating HTML auto-post form responses (thanks @matthid! ❤️)

  • OpenIddict 7.0 preview 2 no longer allows dynamically overriding the prompt value when using OAuth 2.0 Pushed Authorization Requests.

Important

To prevent login endpoint -> authorization endpoint loops, developers are invited to update their authorization endpoint MVC action to 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. For instance:

// 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)
    });
}