Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dependency Injection on AuthorizationOptions Requirement in DotNet Core

I have a .NET core project and am trying to create a custom policy using AuthorizationOptions as shown in the documentation located here:

ASP.NET.Core Authorization - Dependency Injection in requirement handlers

The examples show setting up an authorization requirement with 1 parameter - a simple int value. My custom requirement requires a string parameter as well as a DbContext object. I want to inject the DbContext into the requirement's constructor at runtime. I am using the Autofac container. I'm not sure how I can achieve this - have tried several approaches and nothing is working so far.

Here is my custom requirement:

public UserNameRequirement(string username, MyDbContext context)
{
    _userName = username;
    _dbContext = context;
}

When setting up the authorization options in Startup.cs ConfigureServices method the documentation shows you register this like so:

services.AddAuthorization(options =>
{
    options.AddPolicy(
        "UserNamePolicy",
        policy => policy.Requirements.Add(new UserNameRequirement("admin", ** want to resolve and inject my DbContext here **)));
}

I am not sure how to achieve this. I've seen this post which is a similar question but it's using ASP.NET 5 and that syntax doesn't work with .net core:

Dependency Injection on AuthorizationOptions

like image 498
Pacificoder Avatar asked Feb 23 '17 18:02

Pacificoder


People also ask

How does .NET Core support dependency injection?

ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies. For more information specific to dependency injection within MVC controllers, see Dependency injection into controllers in ASP.NET Core.

Is dependency injection built-in .NET Core?

The Microsoft dependency injection container is built into ASP.NET Core. It allows services to be registered with the IServiceCollection . When a service registered is requested, the container creates and injects an instance of that service.

What is default dependency injection in .NET Core?

Dependency injection (also known as DI) is a design pattern in which a class or object has its dependent classes injected (passed to it by another class or object) rather than create them directly. Dependency injection facilitates loose coupling and promotes testability and maintenance.

How would you apply an authorization policy to a controller in an ASP.NET Core application?

Role-Based Authorization in ASP.NET Core You can specify what roles are authorized to access a specific resource by using the [Authorize] attribute. You can even declare them in such a way that the authorization evaluates at the controller level, action level, or even at a global level. Let's take Slack as an example.


2 Answers

OK, I'm going to make an assumption here, and that is that you need to inject an instance of MyDbContext in UserNameRequirement to perform the business logic.

If this is the case, then it means UserNameRequirement both holds the data - in your case the username - and does the authorization logic. An example of this in ASP.NET Core is the ClaimsAuthorizationRequirement.

The solution to this is to separate this into two classes - on one side the requirement that just holds the data associated with the requirement, on on the other side the authorization handler. As a note, even if we'll go through it, what I'm describing is available in the official ASP.NET Core docs.

So the requirement class could look something like:

public class UserNameRequirement : IAuthorizationRequirement
{
    public UserNameRequirement(string userName)
    {
        UserName = userName;
    }

    public string UserName { get; }
}

and the handler class would be:

public class UserNameRequirementHandler : AuthorizationHandler<UserNameRequirement>
{
    private readonly MyDbContext _dbContext;

    public UserNameRequirementHandler(MyDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UserNameRequirement requirement)
    {
        var userName = requirement.UserName;

        // Use _dbContext to perform business logic
    }
}

The next and last part is to register the handler in the container:

services.AddSingleton<IAuthorizationHandler, UserNameRequirementHandler>();

The effect of doing this is that you can now add your requirement to the policy without worrying about the DbContext:

services.AddAuthorization(options =>
{
    options.AddPolicy(
        "UserNamePolicy",
        policy => policy.Requirements.Add(new UserNameRequirement("admin")));
}

Internally, ASP.NET will then resolve all the handlers associated with that requirement through the container, so the instance of MyDbContext will be available to you in the handler, allowing you to perform the business logic as you see fit.

Hopefully, my assumption is correct, and this helps you.

Edit:

Henry Roux made a good point in a comment below regarding the fact that if the UserNameRequirementHandler is registered as a singleton, then a single instance of MyDbContext will be used, and that could lead to issues. Make sure you register your authorization handlers with the appropriate lifecycle.

like image 154
Mickaël Derriey Avatar answered Oct 22 '22 19:10

Mickaël Derriey


You can use also the GetRequiredService method:

public class ExampleRequirement : AuthorizationHandler<ExampleRequirement>, IAuthorizationRequirement
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ExampleRequirement requirement)
    {
        UserManager<ApplicationUser> UserManager = ((ActionContext)context.Resource).HttpContext.RequestServices.GetRequiredService<UserManager<ApplicationUser>>();

        // you can work with the users ...      

        return Task.CompletedTask;
    }
}
like image 1
SZL Avatar answered Oct 22 '22 21:10

SZL