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:
- For ASP.NET Core, an OAuth 2.0 base handler developed by Microsoft that we massively use for the aspnet-contrib project (more than 80 OAuth 2.0 services are supported at the time of writing!)
- For ASP.NET Core, an OpenID Connect handler developed by Microsoft.
- For ASP.NET 4.x/OWIN, an OpenID Connect middleware maintained by Microsoft.
- For console and GUI applications, a certified OpenID Connect client developed by Duende Software.
- For Blazor WASM applications, an OpenID Connect integration based on the oidc-client-js library.
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
orc_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.
Can you tell me more about this new client?
Put simply, the OpenIddict client is closely modeled after the OpenIddict server and validation stacks and reuses many of their core concepts, including their extremely powerful events model, their modular approach and the host/transport-agnostic design of the core server and validation packages.
Concretely:
The OpenIddict client will be usable with any OAuth 2.0 and OpenID Connect server (based on OpenIddict or not) and will heavily use client/server negotiation to adapt its validation routines to the targeted authorization servers to guarantee maximum compatibility while ensuring the best level of security.
The OpenIddict client currently supports ASP.NET Core 2.1 and higher and native support for ASP.NET 4.x/OWIN will be added in the next few weeks. I also started working on a Blazor WASM prototype and while there'll be limitations (e.g Blazor WASM's encryption/signing support is extremely limited), things are really promising.
The code, implicit and hybrid flows are all natively supported (except
response_type=token
, which is unsafe) and OpenIddict has built-in logic to select the best flow to use (unlike the ASP.NET Core OIDC handler that uses the hardcodedresponse_type=id_token
). It's worth noting the OpenIddict client was designed to allow implementing additional flows (like the OpenID Connect Client-Initiated Backchannel Authentication Flow) without requiring massive design changes.The OpenIddict client will allow supporting multiple static authorization servers (I currently don't plan on adding dynamic client registration support, but it's something that may be added in a future version).
The OpenIddict client uses a different approach for handling authorization callbacks: while the OAuth 2.0 and OIDC handlers developed by Microsoft typically handle everything for you by flowing the
ClaimsPrincipal
extracted from identity tokens/userinfo responses to another authentication handler (in most cases, an instance of the cookies authentication handler), the OpenIddict client won't have this logic and will offer exactly the same approach as the OpenIddict server stack by encouraging users to be involved in the creation of the authentication cookie during the callback handling.
Concretely, the OpenIddict client will offer a pass-through mode to allow handling callbacks in a custom MVC action or minimal API handler. Here's an example of such an action:
1 | // Note: this controller uses the same callback action for all providers |
By doing that, developers will have greater control over what's actually stored in their authentication cookies without requiring a claims mapping feature similar to what's used by the MSFT OIDC handler, that can sometimes be hard to use when you need to restore claims that are removed by the OIDC handler to help ensure the resulting authentication cookies don't hit cookie size limits.
The OpenIddict client will be fully suitable for delegation-only scenarios where authentication is not needed or where the identification of the user who authorized the client is not possible (typically, OAuth 2.0-only servers that don't have a userinfo endpoint). In this case, no information about the user will be available but the tokens will still be available to perform any API call on his behalf.
Currently, only the authorization/authentication part is fully implemented: userinfo will come in the next few weeks and logout support at a later date.
What's next?
I plan on working on a prototype evaluating the OpenIddict client as a potential replacement of the OAuth 2.0 base handler developed by Microsoft for the aspnet-contrib social providers. While the OpenIddict client is certainly a bit more complex, doing so would have interesting advantages:
By being natively compatible with multiple environments (ASP.NET Core, ASP.NET 4.x, Blazor WASM), a broader audience of developers could be reached at no additional cost. This could be a nice migration option for those using the OWIN OAuth providers (by which the aspnet-contrib providers were initially inspired), that are sadly no longer under active development.
Unlike the OAuth 2.0 base handlers, the OpenIddict client natively supports the
client_secret_basic
client authentication method, which would save us from having code that is needed just to attach the client credentials to theAuthorization
header of token requests in all the providers that don't supportclient_secret_post
.By being natively composable, the events model could be used to eliminate code is not strictly required for the social providers to work (e.g if we need to simply attach a custom parameter, we shouldn't have to also send the HTTP request and handle the response in each provider).
The story for social providers supporting both OAuth 2.0 and OpenID Connect would be much better than with the OAuth 2.0 base handler, as they would automatically benefit from all the validation checks implemented in the core OpenIddict client without requiring custom code.
As the OpenIddict client supports both static and dynamic server configuration, social providers that implement discovery could be easily updated to use dynamic configuration, which is not possible with the OAuth 2.0 base handler (that requires hardcoded endpoints).
This will certainly be points I'll discuss with Martin Costello - who co-owns the aspnet-contrib OAuth 2.0 providers with me - once I have a fully working prototype.
You can see the OpenIddict client in action in the sandbox project. If you're interesting in sharing your feedback, please don't hesitate to do so by posting a message in the dedicated GitHub discussion.