-
Notifications
You must be signed in to change notification settings - Fork 215
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
Multiple calls made to OpenIdConnectEvents handlers when using dotnet 7 #1995
Comments
Thanks for the clear repro steps @petedishman For the change of behavior between net6.0 and net7.0, @Tratcher, would you have an idea of what is going on? |
@jennyf19 FYI ... |
@jmprieur could this be related to the IOptions concurrency issues you were having, modifying options instances after resolving them from DI? |
no, @Tratcher : we fixed those. We are using a concurrent dictionary (they were not developer facing options,. |
I'm suspicious of this code path: Lines 414 to 419 in 37b0d33
microsoft-identity-web/src/Microsoft.Identity.Web/Resource/OpenIdConnectMiddlewareDiagnostics.cs Lines 92 to 97 in 37b0d33
Or: microsoft-identity-web/src/Microsoft.Identity.Web/MergedOptions.cs Lines 113 to 125 in 37b0d33
|
The MiddlewareDiagnostics stuff wasn't turned on in my tests, so those paths weren't being hit. That's led me to look at how that method is called and in particular the IOptions/Configuration setup. I created another test project https://github.com/petedishman/OptionsTest They set up DI with options like this:
and then concurrently create multiple instances of When I run the test program using the v6 libraries, I get this output:
When I run the program with the v7 libraries, I get this:
So, with the v6 libraries the configuration callback passed to Interestingly, if after creating my instances concurrently, I then create another one it no longer re-runs the This seems like a regression in the v7 code, but I've not been able to track down where that is yet. |
The new .NET 7 way of configuring the authentication options also allows for a race condition to happen, which breaks the Authorization Code Flow of for the OpenIdConnect authentication handler registered through var codeReceivedHandler = options.Events.OnAuthorizationCodeReceived;
options.Events.OnAuthorizationCodeReceived = async context =>
{
var tokenAcquisition = context!.HttpContext.RequestServices.GetRequiredService<ITokenAcquisitionInternal>();
await tokenAcquisition.AddAccountToCacheFromAuthorizationCodeAsync(
context,
options.Scope,
openIdConnectScheme
).ConfigureAwait(false);
// If the containing configuration method is called multiple times, it will
// call this block again through the codeReceivedHandler of the `options.Events.
// OnAuthorizationCodeReceived` configured through the options factory
// of a different request
await codeReceivedHandler(context).ConfigureAwait(false);
}; As I've mentioned before, this leads to the handler calling the IDP multiple times, which fails the second time because the Authorization Code has already been used. Because we run multiple healthchecks on our containers at- and after startup, we ran into this issue, breaking the authorization code flow. Our workaround is loading the configuration initially, before the host is started: private static void FixMicrosoftIdentityOptionsMonitorRaceCondition(IServiceProvider services)
{
var options = services.GetService<IOptionsMonitor<OpenIdConnectOptions>>();
// By initializing the options before the application starts, we ensure that
// no race condition can occur.
options.Get(OpenIdConnectDefaults.AuthenticationScheme);
} |
@StrawbrryFlurry That matches what we were seeing too. This wasn't initially showing up in local dev, but as soon as we pushed to AWS the load balancer healthchecks all kicked in concurrently and triggered this problem. I hadn't thought of manually requesting the options first but that should help as a temporary workaround. Thanks! |
@petedishman can you file an issue in https://github.com/dotnet/runtime with your TestOptions repro? There's one change in that area that may have regressed this. dotnet/runtime#66688 |
dotnet/runtime#79529 has been identified as a regression in .NET 7 and fixed for .NET 8. It's currently being considered for a patch in .NET 7. I encourage anyone with an impacted application to comment/vote on that issue. @jmprieur this issue can be closed. |
Closing as external |
@petedishman , @tarekgh, @davidkarlsson, @StrawbrryFlurry @StrawbrryFlurry : where do you call |
@jmprieur We call the method after the Service Provider has been built and before the app has started. e.g: var builder = WebApplication.CreateBuilder(args);
// ...
var app = builder.Build();
// ..
FixMicrosoftIdentityOptionsMonitorRaceCondition(app.Services);
// ..
app.Run(); |
@StrawbrryFlurry thanks a ton for the work around, I was banging my head against the wall trying to figure this one out. I was seeing the error:
|
Hi. Is this fixed already? |
It should be. |
As the error is quite "random" in my case, I used @StrawbrryFlurry 's workaround and since then I didn't remove it. |
Is there a chance that this is related to my other issue #2456 ? I'm running .NET 7 in that, but I'm on the latest version of it via VS 2022. EDIT: It does NOT appear to be the same. When trying the |
Microsoft.Identity.Web Library
Microsoft.Identity.Web
Microsoft.Identity.Web version
1.25.10
Web app
Sign-in users and call web APIs
Web API
Not Applicable
Token cache serialization
Not Applicable
Description
Since upgrading to dotnet 7, configured event handlers on
OpenIdConnectEvents
can be called multiple times depending on how many concurrent requests hit the web app when it's just started.If a single request hits the app after startup, then the callback handler will be called once on all subsequent requests.
If multiple concurrent requests hit the app immediately after startup then this can trigger multiple calls to any configured handlers.
This is a change of behaviour from dotnet 6.
Given an application setup like below:
If the app is started and immediately hit with 10 concurrent requests, then future requests will each trigger multiple calls to the
OnRedirectToIdentityProvider
handler. The same applies to all of the other handlers inOpenIdConnectEvents
too.The
msIdentityOptions
callback above is also being called multiple times in this situation which it wasn't before (although see note below). It doesn't matter how this callback configures the event handlers they'll still be called multiple times too.A secondary problem, but one that also exists in dotnet 6, so nothing has actually changed, is that in the above example the
msIdentityOptions
callback is always called at least twice regardless of how many concurrent requests hit the app after startup.This appears to be due to both the
AddMicrosoftIdentityWebApp()
and theEnableTokenAcquisitionToCallDownstreamApi(0
callingbuilder.Services.Configure(opendIdConnectScheme, configureMicrosoftIdentityOptions);
so that the configuration callback ends up being registered twice and therefore run twice.Reproduction steps
The project in https://github.com/petedishman/ms-identity-web-bug reproduces this problem.
It includes a web app that sets up authentication and then a test project that fires up the app and pushes concurrent requests to it and checks how many times the various callbacks were called.
Dotnet 7 gives these test results in VS:
But, if both the projects are changed to use dotnet 6, then you get these results:
The failing tests in the dotnet 6 version are due to the msIdentityOptions configuration callback always being called twice when I'd expect it to only ever get called once.
Error message
No particular error messages are thrown, or rather it depends on what you do in the handlers/callback methods that are called multiple times.
With firing concurrent requests in on startup of the app I am still sometimes getting the
Collection was modified; enumeration operation may not execute
error raised in #1957 despite running on 1.25.10Id Web logs
No response
Relevant code snippets
Regression
No response
Expected behavior
Configuration callbacks should only be called once regardless of the number of concurrent requests that first hit the app.
Configured
OpenIdConnectEvents
handlers should also only ever be called once per requestThe text was updated successfully, but these errors were encountered: