Introducing system integration support for the OpenIddict client

Note: this blog post was updated to use the new record-based APIs introduced in OpenIddict 4.5.

When I unveiled the new OpenIddict client stack a year ago, I mentioned that one of the core design goals was to avoid coupling it to ASP.NET Core to eventually allow using it basically everywhere. With the release of OpenIddict 4.1, I'm making one additional step towards this goal by adding experimental support for Windows and Linux applications.

Why is a dedicated system integration package necessary?

While the OpenIddict client can already be used as-is to implement non-interactive flows like password or client credentials (thanks to its dedicated APIs in OpenIddictClientService), interactive flows like the code, hybrid or implicit flows are more complicated to implement, as they typically require launching the system browser (or using some sort of web view) to redirect the user to the authorization server and handling the authorization callback, which is generally implemented using an embedded web server or by registering a custom protocol URI scheme.

Leaving these critical parts as an exercise wouldn't offer a great experience. To avoid that, the new OpenIddict.Client.SystemIntegration package takes care of launching the user's preferred browser and handles the authorization responses returned by the identity provider to the protocol URI scheme associated with the application (or posted to the embedded web server), in a completely transparent way.

Once configured, doing a complete code flow dance should be as easy as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
try
{
// Ask OpenIddict to initiate the authentication
// flow (typically, by starting the system browser).
var result = await _service.ChallengeInteractivelyAsync(new()
{
ProviderName = provider
});

// Wait for the user to complete the authorization process.
var response = await _service.AuthenticateInteractivelyAsync(new()
{
Nonce = result.Nonce
});

MessageBox.Show($"Welcome, {response.Principal.FindFirst(ClaimTypes.Name)!.Value}.",
"Authentication successful", MessageBoxButton.OK, MessageBoxImage.Information);
}

catch (ProtocolException exception) when (exception.Error is Errors.AccessDenied)
{
MessageBox.Show("The authorization was denied by the end user.",
"Authorization denied", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}

Read more

OpenIddict 4.0 general availability

Two years after OpenIddict's last release, I'm very pleased to announce that 4.0 is now generally available! 🎉

What's new?

While both the server and validation stacks include many internal improvements (some of them were described in OpenIddict 4.0 preview1 is out), the most visible change of this release is the addition of the new client stack and its 17 web providers that aim at offering an alternative to the aspnet-contrib providers (additional services will be added in future versions of OpenIddict):

Provider name
ApplePayPal
Amazon CognitoPro Santé Connect
DeezerReddit
GitHubStackExchange
GoogleTrakt
KeycloakTwitter
LinkedInWordPress
Microsoft Accounts/Azure ADYahoo
Mixcloud

If you're interested in learning more about the new OpenIddict client, don't miss these blog posts:

To see the OpenIddict client in action, you can give the OpenIddict samples a try, as most of them have been updated to use it.

Migration

While OpenIddict 4.0 comes with some breaking changes, the migration process should be fairly easy. To help users with this process, an OpenIddict 4.0 migration guide was added to the documentation.

OpenIddict 4.0 is fully compatible with ASP.NET Core 2.1 (on .NET Framework), ASP.NET Core 3.1, ASP.NET Core 6.0 and ASP.NET Core 7.0, so the migration can be done without having to upgrade to the latest .NET runtime/ASP.NET Core version.

Support

With OpenIddict 4.0 being now generally available, the previous version, OpenIddict 3.0, stops being supported and won't receive bug fixes or security updates. It is recommended to migrate to OpenIddict 4.0 to continue receiving bug and security fixes.

Acknowledgements

As always, a new major release is a great opportunity to thank all the sponsors who have helped keep OpenIddict free for everyone. Every contribution counts – of course! – but I'd like to dedicate this release to 3 sponsors in particular, who have a special place in my heart:

  • Volosoft: it might sound surprising, but Volosoft is the first and only company that has spontaneously offered to sponsor the project while evaluating OpenIddict as a replacement for IdentityServer4 in their (very!) popular ABP Framework project. Without Volosoft's incredible support, it's very likely the project would no longer be as active as it today (or even free for everyone).

  • Dovydas Navickas: Dovydas is one of the very first OpenIddict sponsors. Even though he stopped using OpenIddict after a big career change some time ago, Dovydas decided to keep sponsoring the project! This level of support is truly amazing so thank you very much Dovydas!

  • Jasmin Savard: Jasmin doesn't use OpenIddict but decided to give away a significant part of the sponsorship he receives as one of the most important Orchard Core contributors to keep OpenIddict free for everyone else. No need to say this dedication leaves me absolutely speechless!

I hope you'll love OpenIddict 4.0 as much as I enjoyed working on it.

Merry Christmas everyone! 🎄 🎁

Getting started with the OpenIddict web providers

Note: this blog post was updated to use the OpenIddict 5.1 packages.

Earlier this year, I unveiled the new web providers that will ship as part of the OpenIddict 4.0 release.

To help users understand the differences between the existing aspnet-contrib providers and the new OpenIddict-based providers, I also posted additional information on the aspnet-contrib repository ; so if you're considering replacing the aspnet-contrib providers by their equivalent in OpenIddict, don't miss it.

Today, we're going to see how to easily get started by creating a minimal application that uses the OpenIddict.Client.WebIntegration package.

At the time of writing, more than 60 providers are already available, but to keep things simple, we'll focus our attention on a single one: GitHub.

Create a new ASP.NET Core application

For this step, we could use one of the Visual Studio templates, but since there's a trend towards doing things the "minimalistic" way, we're only going to add two files: a .csproj file containing the package references we'll need and a Program.cs file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.0" />
<PackageReference Include="OpenIddict.AspNetCore" Version="5.1.0" />
<PackageReference Include="OpenIddict.EntityFrameworkCore" Version="5.1.0" />
</ItemGroup>

</Project>
1
2
3
4
5
6
7
8
9
10
11
12
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseUrls("https://localhost:44381/");

builder.Services.AddAuthentication();
builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

await app.RunAsync();

If you run this application as-is, all you'll get is a 404 response as we didn't register any HTTP handler. We'll get back to that in a few minutes.

Read more

OpenIddict 4.0 preview1 is out

Earlier today, the first OpenIddict 4.0 preview was pushed to NuGet.org 🎉

As always, all the changes can be found on GitHub but here's a recap of the most interesting ones:

What's new?

OpenIddict now includes a client stack

The introduction of a client stack in 4.0 is by far the most exciting change (well, at least for me 😁).

If you missed my previous post, I'd suggest reading it first as it details the motivation for creating it and the differences with existing offers like the ASP.NET Core OpenID Connect or OAuth 2.0 handlers.

The OpenIddict client will come with a companion package to simplify well-known Web services integrations

I had mentioned in my previous blog post that I was interested in developing an alternative to the aspnet-contrib providers that would be based on the new OpenIddict client instead of the ASP.NET Core 2.0 base handler and would have the following advantages:

  • Thanks to its dual-protocol nature, the OpenIddict client could enforce all the security checks introduced by OpenID Connect that a simple OAuth 2.0 client implementation like the ASP.NET Core OAuth 2.0 base handler wouldn't support, which means we could easily support both OAuth 2.0 and OpenID Connect providers without sacrificing security.

  • By being natively compatible with ASP.NET Core (from 2.1 to 7.0) and OWIN/Katana 4.2, the providers built on top of the OpenIddict client could be used in both recent ASP.NET Core apps and legacy ASP.NET >= 4.6.1 apps, making them usable in many more applications than the aspnet-contrib providers, that always target the most recent ASP.NET Core version.

  • Unlike the OAuth 2.0 base handler developed by Microsoft, the OpenIddict client comes with a composable events model that could allow eliminating code that we needed to copy in the aspnet-contrib providers when overriding certain OAuthHandler methods.

The good news is that this idea materialized as a dedicated OpenIddict.Client.WebIntegration package that depends on OpenIddict.Client and includes the logic necessary to deal with all the non-standard aspects of each supported provider. Unlike the aspnet-contrib providers, OpenIddict.Client.WebIntegration relies on Roslyn Source Generators to dynamically create all the plumbing code, which makes it much easier to maintain. If you're interested in learning more about the technical details, don't miss the PR that introduced it.

If you're already familiar with the aspnet-contrib providers, migrating to the OpenIddict-based providers shouldn't be too complicated:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
services.AddOpenIddict()

// Register the OpenIddict core components.
.AddCore(options =>
{
// Configure OpenIddict to use the Entity Framework Core stores and models.
// Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities.
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>();
})

// Register the OpenIddict client components.
.AddClient(options =>
{
// Enable the redirection endpoint needed to handle the callback stage.
//
// Note: to mitigate mix-up attacks, it's recommended to use a unique redirection endpoint
// address per provider, unless all the registered providers support returning an "iss"
// parameter containing their URL as part of authorization responses. For more information,
// see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.4.
options.SetRedirectionEndpointUris(
"/signin-local",
"/signin-github",
"/signin-google",
"/signin-reddit",
"/signin-twitter");

// Register the signing and encryption credentials used to protect
// sensitive data like the state tokens produced by OpenIddict.
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();

// Register the ASP.NET Core host and configure the ASP.NET Core-specific options.
options.UseAspNetCore()
.EnableStatusCodePagesIntegration()
.EnableRedirectionEndpointPassthrough();

// Register the Web providers integrations.
options.UseWebProviders()
.AddGitHub(new()
{
ClientId = "[client identifier]",
ClientSecret = "[client secret]",
RedirectUri = new Uri("https://localhost:44381/signin-github", UriKind.Absolute)
})
.AddGoogle(new()
{
ClientId = "[client identifier]",
ClientSecret = "[client secret]",
RedirectUri = new Uri("https://localhost:44381/signin-google", UriKind.Absolute),
Scopes = { Scopes.Profile }
});
});

Read more

Introducing the OpenIddict client

When I unveiled the OpenIddict 3.0 roadmap three years ago, I mentioned that having an OpenIddict client would be a very nice addition but that implementing it as part of 3.0 wasn't realistic. Today, I'm very happy to announce that the OpenIddict client will ship as part of OpenIddict's next major version.

Why a new client?

A few client libraries already exist for .NET, including:

So why do we need another one? Am I reinventing the wheel by introducing another OIDC client stack?

Well, that's definitely a legit question. First, I have to say that these implementations work just fine and that I've used them happily other the years. Actually, I even contributed to some of these implementations multiple times so I have a fairly good experience working with them.

In a nutshell, here's what motivated me:

  • While all these libraries offer very specialized implementations, none of them offers a unified experience that allowing sharing a common code base usable on, say, ASP.NET Core and Blazor WASM applications: what you learned about the ASP.NET Core OIDC handler is not applicable to Blazor WASM, that uses a completely different OIDC client stack under the hood.

  • The OIDC integration for Blazor WASM uses oidc-client-js, that was archived in June 2021 and is no longer supported. While the Blazor OIDC wrapper itself is supported by Microsoft, the library it uses under the hood is not and won't receive any bug or security fix, which is a bit surprising to me, considering its author is still sponsored by Microsoft.

  • While it's supported by Microsoft, the Blazor OIDC wrapper doesn't actually get much love and suffers from annoying design issues that affect OpenIddict users that the team is unwilling to fix.

  • The OAuth 2.0 base handler for ASP.NET Core offers a straightforward API for creating derived OAuth 2.0 clients that we successfully used in the aspnet-contrib social providers. Unfortunately, its simplicity comes at a cost: it's not composable. Concretely, it means that every time we need to customize something (e.g adding a parameter to the token request), we end up duplicating more code than what we should (e.g to add a token request parameter, you also need to take care of sending the HTTP request and handling the response, which is something the OAuth 2.0 base handler does for you if you don't override the OAuthHandler<T>.ExchangeCodeAsync(OAuthCodeExchangeContext context) method).

  • There's also another downside with the OAuth 2.0 base handler: it doesn't support OpenID Connect. Yet, we received contributions to add OAuth 2.0 providers that also support OpenID Connect: while ASP.NET Core has a dedicated OIDC handler that is more appropriate for these cases, many people like the simpler registration story the OAuth 2.0 base handler and the aspnet-contrib providers offer. Retrospectively, we shouldn't have accepted these contributions as these providers don't benefit from the additional security checks a real OpenID Connect implementation requires (e.g nonce, at_hash or c_hash validation, etc.)

  • The OAuth 2.0 base handler doesn't support the OAuth 2.0 Authorization Server Metadata specification that backported the OpenID Connect server discovery feature to the OAuth 2.0 world and that allows finding the location of the authorization and token endpoints dynamically, without having to hardcode them. While the adoption is slow, I'd expect more and more services to support it in the next few years.

Read more