# Migrate to OpenIddict 4.0

## What's new?

The most important changes introduced in 4.0 can be found here.

##### Note

Unless you're using MongoDB, migrating to OpenIddict 4.0 doesn't require making changes to your database.

## Update your packages references

For that, update your `.csproj` file to reference the `OpenIddict` 4.x packages. For instance:

    <ItemGroup>
      <!-- OpenIddict 3.x: -->
      <PackageReference Include="OpenIddict.AspNetCore" Version="3.1.1" />
      <PackageReference Include="OpenIddict.EntityFrameworkCore" Version="3.1.1" />

      <!-- OpenIddict 4.x: -->
      <PackageReference Include="OpenIddict.AspNetCore" Version="4.0.0" />
      <PackageReference Include="OpenIddict.EntityFrameworkCore" Version="4.0.0" />
    </ItemGroup>

##### Note

Migrating to ASP.NET Core 7.0 is not required, as OpenIddict 4.0 is still natively compatible with ASP.NET Core 2.1 (.NET Framework-only), ASP.NET Core 3.1 and ASP.NET Core 6.0. Moving to a newer .NET runtime or ASP.NET Core can be done separately for a simpler/decoupled upgrade:

Web framework version | .NET runtime version
--- | ---
ASP.NET Core 2.1 | .NET Framework 4.6.1
ASP.NET Core 2.1 | .NET Framework 4.7.2
ASP.NET Core 2.1 | .NET Framework 4.8
ASP.NET Core 3.1 | .NET Core 3.1
ASP.NET Core 6.0 | .NET 6.0
ASP.NET Core 7.0 | .NET 7.0
Microsoft.Owin 4.2 | .NET Framework 4.6.1
Microsoft.Owin 4.2 | .NET Framework 4.7.2
Microsoft.Owin 4.2 | .NET Framework 4.8

## Update your endpoint URIs

OpenIddict 4.0 introduces a behavior change that affects how endpoint URIs are computed and resolved. For more information about this change, read Breaking changes in OpenIddict 4.0 impacting how URIs are handled.

In most cases, tweaking your code should be limited to removing the leading slashes in your endpoint paths to account for the new logic:

    services.AddOpenIddict()
        .AddServer(options =>
        {
            // OpenIddict 3.x:
            options.SetAuthorizationEndpointUris("/connect/authorize")
                   .SetDeviceEndpointUris("/connect/device")
                   .SetIntrospectionEndpointUris("/connect/introspect")
                   .SetLogoutEndpointUris("/connect/logout")
                   .SetTokenEndpointUris("/connect/token")
                   .SetUserinfoEndpointUris("/connect/userinfo")
                   .SetVerificationEndpointUris("/connect/verify");

            // OpenIddict 4.x:
            options.SetAuthorizationEndpointUris("connect/authorize")
                   .SetDeviceEndpointUris("connect/device")
                   .SetIntrospectionEndpointUris("connect/introspect")
                   .SetLogoutEndpointUris("connect/logout")
                   .SetTokenEndpointUris("connect/token")
                   .SetUserinfoEndpointUris("connect/userinfo")
                   .SetVerificationEndpointUris("connect/verify");
        });

## Remove calls to `AddClaim(s)` that specify a list of destinations:

As explained in OpenIddict 4.0 preview1 is out, the `AddClaim(s)` extensions that accepted a `destinations` parameter have been removed in 4.0.

Instead, developers are encouraged to use the new one-shot `SetDestinations()` extension for `ClaimsIdentity` and `ClaimsPrincipal` (that must be called after all the claims have been added to the identity/principal):

    var identity = new ClaimsIdentity(
        authenticationType: TokenValidationParameters.DefaultAuthenticationType,
        nameType: Claims.Name,
        roleType: Claims.Role);

    identity.SetClaim(Claims.Subject, await _userManager.GetUserIdAsync(user))
            .SetClaim(Claims.Email, await _userManager.GetEmailAsync(user))
            .SetClaim(Claims.Name, await _userManager.GetUserNameAsync(user))
            .SetClaims(Claims.Role, (await _userManager.GetRolesAsync(user)).ToImmutableArray());

    identity.SetScopes(result.Principal.GetScopes());
    identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());

    identity.SetDestinations(static claim => claim.Type switch
    {
        // Allow the "name" claim to be stored in both the access and identity tokens
        // when the "profile" scope was granted (by calling principal.SetScopes(...)).
        Claims.Name when claim.Subject.HasScope(Scopes.Profile)
            => new[] { Destinations.AccessToken, Destinations.IdentityToken },

        // Otherwise, only store the claim in the access tokens.
        _ => new[] { Destinations.AccessToken }
    });

## If applicable, update your OpenIddict MongoDB authorizations

To match the casing used by the other properties, the name used in the BSON representation of the `OpenIddictMongoDbAuthorization.CreationDate` property was fixed to use camel case (i.e `creation_name` instead of `CreationDate`). To ensure the existing authorizations are correctly updated to use the new name, the following script can be used to update all the existing authorizations at once very efficiently:

    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Options;
    using MongoDB.Bson;
    using MongoDB.Driver;
    using OpenIddict.MongoDb;

    var services = new ServiceCollection();
    services.AddOpenIddict()
        .AddCore()
        .UseMongoDb()
        .UseDatabase(new MongoClient("mongodb://localhost:27017").GetDatabase("openiddict"));

    await using var provider = services.BuildServiceProvider();
    var context = provider.GetRequiredService<IOpenIddictMongoDbContext>();
    var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictMongoDbOptions>>().CurrentValue;
    var database = await context.GetDatabaseAsync(CancellationToken.None);

    var authorizations = database.GetCollection<BsonDocument>(options.AuthorizationsCollectionName);
    await authorizations.UpdateManyAsync(
        filter: Builders<BsonDocument>.Filter.Empty,
        update: Builders<BsonDocument>.Update.Rename("CreationDate", "creation_date"));

## If applicable, replace references to `Portable.BouncyCastle` by `BouncyCastle.Cryptography`

While previous versions of OpenIddict used the unofficial `Portable.BouncyCastle` package maintained by Claire Novotny (which was the best .NET Standard-compatible option at the time), OpenIddict 4.0 was updated to use the official package, BouncyCastle.Cryptography, that was released in November 2022 with complete .NET Standard 2.0 support.

If your application uses `Portable.BouncyCastle`, it is strongly recommended to migrate to `BouncyCastle.Cryptography` to avoid type conflicts.

## If applicable, update your custom stores to use the updated signatures

OpenIddict 4.x fixes the nullability annotations of `IOpenIddictApplicationStore.GetAsync()`, `IOpenIddictAuthorizationStore.GetAsync()`, `IOpenIddictScopeStore.GetAsync()` and `IOpenIddictTokenStore.GetAsync()` to return `ValueTask<TResult?>` instead of `ValueTask<TResult>`.

Developers who implemented these interfaces and enabled nullable references are invited to update the signature of the `GetAsync()` methods:

    // OpenIddict 3.x:
    ValueTask<TResult> GetAsync<TState, TResult>(
        Func<IQueryable<TApplication>, TState, IQueryable<TResult>> query,
        TState state, CancellationToken cancellationToken);

    // OpenIddict 4.x:
    ValueTask<TResult?> GetAsync<TState, TResult>(
        Func<IQueryable<TApplication>, TState, IQueryable<TResult>> query,
        TState state, CancellationToken cancellationToken);

While not required, it is recommended to also update implementations of `IOpenIddictApplicationStore` to use the updated parameter names for `FindByPostLogoutRedirectUriAsync()`, `FindByRedirectUriAsync()`, `SetPostLogoutRedirectUrisAsync()` and `SetRedirectUrisAsync()`:

    // OpenIddict 3.x:
    IAsyncEnumerable<TApplication> FindByPostLogoutRedirectUriAsync(string address, CancellationToken cancellationToken);
    IAsyncEnumerable<TApplication> FindByRedirectUriAsync(string address, CancellationToken cancellationToken);
    ValueTask SetPostLogoutRedirectUrisAsync(TApplication application, ImmutableArray<string> addresses, CancellationToken cancellationToken);
    ValueTask SetRedirectUrisAsync(TApplication application, ImmutableArray<string> addresses, CancellationToken cancellationToken);

    // OpenIddict 4.x:
    IAsyncEnumerable<TApplication> FindByPostLogoutRedirectUriAsync(string uri, CancellationToken cancellationToken);
    IAsyncEnumerable<TApplication> FindByRedirectUriAsync(string uri, CancellationToken cancellationToken);
    ValueTask SetPostLogoutRedirectUrisAsync(TApplication application, ImmutableArray<string> uris, CancellationToken cancellationToken);
    ValueTask SetRedirectUrisAsync(TApplication application, ImmutableArray<string> uris, CancellationToken cancellationToken);

## Consider migrating to the new OpenIddict client (optional)

OpenIddict 4.0 introduces a new client stack that is natively compatible with all supported versions of ASP.NET Core (2.1 on .NET Framework, 3.1, 6.0 and 7.0) and `Microsoft.Owin` 4.2 (which means it can also be used on ASP.NET 4.6.1 and higher).

For more information, read OpenIddict 4.0 general availability.