-
Notifications
You must be signed in to change notification settings - Fork 597
Enable IClaimsTransformer to be specified in DI instead of only options #863
Comments
@jburbach Can you include what your IUserRepository service looks like and how you register it in DI? |
Closing because we cannot reproduce this. Please re-open if you have more info. Thanks! |
@Eilon @HaoK My apologies for not responding quicker. Left for vacation and wasn't checking messages. I register it like so and here is my UserRepo
|
How are you registering EmployeesAppContext in Startup, my guess this is the root issue... |
The root cause of the issue you're seeing seems quite clear for me: your claims transformer - necessarily a singleton by definition, since it's attached to One way to solve this issue is to avoid injecting public async Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context) {
var repository = context.Context.RequestServices.GetRequiredService<IUserRepository>();
// ...
} Note that it's not specific to the claims transformation middleware: it impacts all the middleware using "services as options", which basically includes all the authentication middleware using the There was a ticket in aspnet/Options tracking a brilliant way to enable this kind of scenario, but it has never been implemented: aspnet/Options#11. Related topic: aspnet-contrib/AspNet.Security.OpenIdConnect.Server#275 |
Hrm it seems bad if claims transformation can't use the scoped DbContext easily. Seems like something we should look at in 1.0.1 |
Although I guess the workaround to just get any scoped services from the context RequestServices doesn't seem that bad, but this seems like this will be a common pitfall. |
Well, it's not just claims transformation, it's everything using "services as options", which impacts the whole stack, not just the security middleware.
Personally, I don't have any particular problem with the service locator pattern (at least, as long as it's not static), but many people consider that as an anti-pattern. Having a cool sugar like the one that was suggested would have been great. Sadly, it's now too late for that. |
I'll modify my code and verify. I agree @HaoK, I think this will be a very common issue people have unless it's made VERY clear in your documentation that they can't do this. |
This appears to have corrected the issue. Thank you @PinpointTownes. There is one other thing I'm running in to that I wasn't sure was related, but now I think it is. There are errors I get when the app first starts that appear to be trying to use the dbcontext to look up the user in the ClaimsTransformer, but the context isn't ready yet. This only happens on the initial browser launch, I can then just refresh and get my SPA and everything works fine from then on. Would you like to see this here or should I open another issue? |
To follow up, I don't think this issue is completely resolved for me. I get seemingly random 500 errors from my API calls that say this:
This is the same error I get on startup, and it happens on the FindUsername call in the ClaimsTransformer. I think that it's from the ClaimsTransformer trying to use the dbcontext before it is ready to be used. No errors occur at all if I remove the claims transformation. My EF registration is just the default setup:
Thoughts? |
In fact, even if I replace the database lookup with something simple like this, which just gets a list of possible permissions from an Enum and adds them to the identity:
On startup I get an error that says:
Like something is trying to access the identity while I'm populating it. I think the async nature of claims transformation is either conflicting with some other design or perhaps it's not being awaited like it should be? |
Any chance you could share your updated transformer and your Startup class (or better a complete repro?) 😄 |
I can't publish my full code, but if it comes to it and you guys need a solution I can try to put something together that reproduces the issue. Was hoping maybe there would be an "aha moment" before it came to that though. :) |
There is an overload to app.UseClaimsTransformation that takes in a delegate to get your claims transfomer, and that seems to run on a per-request basis. So your ClaimsTransformer does not need to be a singleton. You still have to use the context as a ServiceLocator, but it can be done outside of your ClaimsTransformer, which can still be registered as scoped. I added this block above my app.UseMvc in Configure:
I just got this working with a rudimentary example, so not sure if there would be issues when used with EF. |
@adamholdenyall That's it! Once we eliminate the claimstransformer as a singleton and have it recreated every request, no more errors and it appears to play perfectly with EF now. I think this is even more evidence that they need to marry this up like @PinpointTownes suggested earlier. The fact that using |
We should give some more love to claims transformation. |
One possible improvement would be to add a new options flag like PreferDITransformer (but with a better name), which would be used instead of the instance found in the options and enable per request transformation:
|
@HaoK alternatively, why not a
|
That's not a bad idea, but if we were going that direction maybe we could just add a new No need to add a flag this way, as you'd be opting into this behavior using the new generic middleware |
Will be handled as part of the big auth 2.0 change, ClaimsTransformation will become a pure service, tracked via #1061 |
I'm not sure whether to post this here or in the EF repo, but I figured I'd start here.
ASP.NET Core RC2 WebAPI/SPA using windows auth, and I'm attempting to do a very basic claims transformation by looking the ID up in my database with my DBContext and adding claims I find there to the windows principal. I am using a repository pattern with EF, all registered as Scoped lifetime, the dbcontext should as well by default, so all methods are sharing the same dbcontext on each request. I get a lot of random EF errors on my _users.FindByUsername() method that say things like
Invalid attempt to call ReadAsync when reader is closed.
InvalidOperationException: BeginExecuteReader requires an open and available Connection. The connection's current state is open.
InvalidOperationException: The connection was not closed. The connection's current state is connecting.
I also get this error at startup, but then can refresh the page and get my SPA like I should:
InvalidOperationException: An attempt was made to use the context while it is being configured. A DbContext instance cannot be used inside OnConfiguring since it is still being configured at this point.
I think this is related to the transform running async and competing with other threads trying to access the db at the same time? I get these errors randomly under load, nothing consistent. If I take the DB lookup out of my claimstransform, zero errors. I tried making my user lookup async and awaiting it, which lessens the amount of errors but doesn't completely solve the issue.
My question is, is what I'm doing not recommended? Should I not be trying to use EF with a ClaimsTransform, or should I have a separate context just for this user lookup? Do I need to scope my dbcontext/repositories differently in DI? I'm curious what the envisioned recommended pattern is for something like this, and if this is possibly an EF Core issue that should work but doesn't.
Apologies if there is something obvious here I'm missing, I've looked through all the documentation I can find for best practices and didn't find anything that recommends something different.
The text was updated successfully, but these errors were encountered: