Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrated OpenIddict server into vanilla .NET 8 Identity Web UI #321

Open
1 task done
karlschriek opened this issue Jun 10, 2024 · 2 comments
Open
1 task done

Comments

@karlschriek
Copy link

Confirm you've already contributed to this project or that you sponsor it

  • I confirm I'm a sponsor or a contributor

Version

5.4.0

Question

Background

We are currently working on using the latest .NET identity web UI (which you can get bundled in your IDE when you create a .NET 8 Blazor Web App) in combination with OpenIddict.

The latest template provides a good base for creating a production-ready UI for users to interact with the IdP, including doing things like registering MFA devices, resetting passwords, confirming email addresses and a whole lot more. (You can read more about it here https://devblogs.microsoft.com/dotnet/whats-new-with-identity-in-dotnet-8/#the-blazor-identity-ui)

Our goal was to take this template as a starting point and then integrate OpenIddict into it. Since OpenIddict fundamentally uses the AspNet identity classes, and extends them with entities such as Appplication, Token etc. we hoped that this would be simple to do (even if for someone who is not intimately familiar with either framework). To a large extent this is true, and the result of that is a sample that can be found here:

https://github.com/karlschriek/openiddict-blazor-server-sample/tree/main/OpenIddict.Blazor.Server

(For comparison, here is what the vanilla identity UI we worked from looks like: https://github.com/karlschriek/openiddict-blazor-server-sample/tree/main/VanillaIdentityUI.Blazor.Server)

In addition to the above, this sample also includes a few other things that we have worked on in the past, but these are not directly relevant to this issue

  • Allows for input of Symmetric and RSA keys via appsettings
  • Allows for DbSeeding via appsettings
  • Allows for configuring AzureAD external login provider via appsettings
  • Adds a "SendGrid" EmailSender implementation (can be switched on in appsettings)
  • Adds the ability to add an MFA device via QR code (vanilla UI only displays a code that user must type in)

Where we need help

While we were able to get the login flows to work correctly, we are having trouble with the anti-forgery token (although I suspect this might rather just be a symptom of something more fundamental that is misconfigured). The identity UI has several places where it uses the anti-forgery token. The simplest one to test against is the "logout" button.

The moment you navigate to https://localhost:7143/ (where the "OpenIddict.Blazor.Server" sample project is hosted) I can see the anti-forgery token being set, with a name like ".AspNetCore.Antiforgery.hLXdGIjSYB8". As soon as the user logs in, the ".AspNetCore.Identity.Application" cookie is also set. If I now logout, I get:

An unhandled exception occurred while processing the request.
AntiforgeryValidationException: The provided antiforgery token was meant for a different claims-based user than the current user.
Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateTokens(HttpContext httpContext, AntiforgeryTokenSet antiforgeryTokenSet)

BadHttpRequestException: Invalid anti-forgery token found when reading parameter "string returnUrl" from the request body as form.
Microsoft.AspNetCore.Http.RequestDelegateFactory+Log.InvalidAntiforgeryToken(HttpContext httpContext, string parameterTypeName, string parameterName, Exception exception, bool shouldThrow)

Similar errors occur on other Blazor components that also require <AntiforgeryToken/>.

This makes me think that either something that we switched off (such as the ".AddIdentityCookies();" extension, which causes an error if use in conjunction with the OpenIddict setup we've added) or some other fundamental mismatch is happening. Would you be able to help us get this sample working correctly? It would go a long way to us being able to take OpenIddict into production.

@kevinchalet
Copy link
Member

kevinchalet commented Jun 10, 2024

Hey,

Since OpenIddict fundamentally uses the AspNet identity classes, and extends them with entities such as Appplication, Token etc. we hoped that this would be simple to do (even if for someone who is not intimately familiar with either framework).

To clarify, while OpenIddict uses an approach similar to Identity for its persistence story (entities, stores), it actually doesn't depend on Identity at all (and doesn't use it).

Would you be able to help us get this sample working correctly?

I'll give the sample a try later today but here's what I think is happening:

  • All the antiforgery tokens - form tokens and cookie tokens - produced by the antiforgery stack are bound to the user identity present when creating them. It's an important security aspect of this feature, tho' it's not new in ASP.NET Core (it was already supported by the ASP.NET 4.x anti-XSRF web helper). Concretely, if a page is rendered with a form antiforgery token generated for "user 1" (with a corresponding cookie token present in the Set-Cookie response header), you'll get an error if you try to submit the form as-is if the user identity changed between the moment the form was generated and the moment you're submitting it.

  • Your antiforgery middleware (app.UseAntiforgery()) is not registered at the right place (i.e it's registered before app.UseAuthentication()), so the user identity may not be properly extracted by the time its validation logic runs. On the other hand, when Blazor asks IAntiforgeryService to generate the antiforgery tokens, the user identity is available (since it executes much later in the pipeline), so you eventually get a mismatch when validating the tokens.

TL;DR, try to move app.UseAntiforgery() after app.UseAuthorization() to see if it helps.

@karlschriek
Copy link
Author

Thanks for the clarification on OpenIddict's use of "Identity", I will definitely keep that in mind.

Also... that fix was much simpler than I expected the answer to be! You are absolutely right, placing app.UseAntiforgery() after app.UseAuthorization() fixes it. (The biggest tragedy of all this is that is that I had misread an error message that actually told me this a few days ago; I went back and recreated it just now and there it was in black and white)

By the way, if you find the sample useful I can also prepare it for this repo. Our full sample is actually a bit more extensive though, and consists of three services:

  1. Authorization Server (i.e. this project)
  2. Client application (also a fully permissioned Blazor Server app, which federates to OpenIddict)
  3. Downstream API (a webapi for which the client application can request an OpenIddict access token

Basically in our use case OpenIddict acts as a central authorization service within a microservices ecosystem. It controls login into various potential client-facing applications, which also call various different downstream WebApis on behalf of the user (using access token).

Let me know if you would be interested in such a sample.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants