I'm trying to write a custom policy for an ASP.NET Core 3.1 web application, using a custom Identity storage provider.
I've tried to wrap my head around the fact that policies in ASP.NET Core are designed to take user informations from an HttpContext
object, when I read this in a MSDN Article:
once you hold a reference to the user, you can always find the username from the claims and run a query against any database or external service
I started writing my own policy (as of now a simple role requirement) injecting the UserManager
into the constructor:
public class RoleHandler : AuthorizationHandler<RoleRequirement>
{
private UserManager<AppUser> UserManager;
public RoleHandler(UserManager<AppUser> usermanager)
{
UserManager = usermanager;
}
}
Now I have a couple problems:
INJECTING A SCOPED SERVICE IN A SINGLETON
Policies are supposed to be lasting for the entire application life, so that would be a Singleton:
services.AddSingleton<IAuthorizationHandler, RoleHandler>();
but the UserManager injected in the policy server is a scoped service and that is not allowed. Solution was very easy, changing the configuration of the policy service from a singleton to a scoped service
services.AddScoped<IAuthorizationHandler, RoleHandler>();
but I don't know whether that cause any issue or not.
WRITING AN ASYNCHRONOUS POLICY HANDLER
This is my implementation of the HandleRequirementAsync
method:
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
{
AppUser user = UserManager.FindByIdAsync(context.User.Identity.Name).Result;
if (user != null)
{
bool result = UserManager.IsInRoleAsync(user, requirement.Role.ToString()).Result;
if (result) context.Succeed(requirement);
}
return Task.CompletedTask;
}
I used Task.Result
but it blocks the thread. I can't use await
because that would make the method returning a Task<Task>
instead of a Task
and I can't change it. How can I solve this?
Don't return Task.CompletedTask
.
When you declare a method as async
, it implicitly returns a Task
when the first await
is hit:
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
{
AppUser user = await UserManager.FindByIdAsync(context.User.Identity.Name);
if (user != null)
{
bool result = await UserManager.IsInRoleAsync(user, requirement.Role.ToString());
if (result) context.Succeed(requirement);
}
}
Task.CompletedTask
is generally used when you need to implement a Task
returning method synchronously, which you are not.