-
Notifications
You must be signed in to change notification settings - Fork 589
Enable IClaimsTransformer to be specified in DI instead of only options #863
Description
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.
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using MyApp.Models.Repositories.Interfaces;
using Microsoft.AspNetCore.Authentication;
namespace MyApp.Authorization
{
public class ClaimsTransformer : IClaimsTransformer
{
private readonly IUserRepository _users;
public ClaimsTransformer(IUserRepository users)
{
_users = users;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
System.Security.Principal.WindowsIdentity windowsIdentity = null;
foreach (var i in context.Principal.Identities)
{
//windows token
if (i.GetType() == typeof(System.Security.Principal.WindowsIdentity))
{
windowsIdentity = (System.Security.Principal.WindowsIdentity)i;
}
}
if (windowsIdentity != null)
{
//find user in database
var appUser = _users.FindByUsername(windowsIdentity.Name);
if (appUser != null)
{
//add all claims from security profile
foreach (var p in appUser.SecurityProfile.Permissions)
{
((ClaimsIdentity)context.Principal.Identity).AddClaim(new Claim(p.Permission, "true"));
}
}
}
return await Task.FromResult(context.Principal);
}
}
}