ASP.NET Core Authorization – Using attributes for handlers and requirements
Dealing with authentication and authorization in ASP.NET Core while working with modern web apps has been much better experience than it was in older versions of ASP.NET. And it’s been further improved with v2. There are few quirks when you work with it enough, but the feeling overall is much better.
Besides official documentation on authorization, I highly recommend Barry Doran’s Authorization workshop. It is an amazing resource to get up to speed with Authorization in ASP.NET Core. You will learn how policies, handlers and requirements work. He also has a great workshop on Authentication as well. Barry is a Microsoft security export and he is in charge of security in .NET. Therefore, you can rest assured that the workshops are valid and reliable.
On the last few projects, I have been heavily reliant on policies, handlers and requirements. And so far, it’s been a joy to use them to deal with authorization. However, I found few use cases where I felt need to make use of Requirements and Handlers via attributes on my controller actions/methods. I didn’t like the code in my actions getting complex and actions themselves starting to get bloated with the authorization code.
If you share the same opinion and you have similar needs, or you are just interested in the approach, or maybe you think it’s not the best approach, read on!
Complete and working sample is available on GitHub.
Maybe you only want to check out the sample or follow along. To do so, check out the GitHub repository – here.
Let’s say we have a simple system where we have only a few entities that matter:
- resources – this could be documents, categories, notes. Whatever you want
Every resource has an owner – user of the system. We want to make sure that only a person who is the owner of a resource can manage the resources, but we also want give administrators ability to manage anyone’s resources.
I mostly work with ASP.NET Core MVC API’s and that’s where I made use of the custom attributes that work with requirements and handlers. However, for sake of simplicity, I will be using ASP.NET Core MVC version 2.0 Auth template with Individual User Accounts option.
We will have Category as our resource model:
And by using a DbContext class, let’s add Categories table:
This class actually inherits from IdentityDbContext, because we want to make use of ASP.NET Core Identity system and all the goodies that come with it.
I can now generate CRUD views and controller connected to Entity Framework Core. This is standard scaffolding which uses our ApplicationDbContext and Category to generate views and controller with standard default CRUD actions.
Let’s see a part of a generated code, illustrating CREATE/POST method inside of CategoriesController:
With that out of the way, let’s take a look at InsertCategoryHandler and the InsertCategoryRequirement:
Handler requires a requirement, regardless of it being an empty class or a class with some kind of logic.
The most important method here is HandleRequirementAsync. Let’s see what it does.
This will first check if the resource is actually a Category. After that, it will get a userId of currently logged in user and check if he has admin access. If the user is admin we will let him pass, because administrators can do all actions.
However, if the user is not an admin, we check the category object itself. Then we check if the Id property is set and if it is, something is wrong, hence we check if Id is a null or empty string. We also check if UserId property of our category matches the UserId of currently logged-in user! If that works then all is good, if not we return with a fail result.
Making use of Requirement/Handler
Now we need to apply our requirement inside of our controller. First, we need an instance of ASP.NET Core’s IAuthorizationService. When you add MVC to DI by using
services.AddMvc() you will be able to use IAuthorizationService via DI. AddMvc calls AddAuthorization which in turn registers DefaultAuthorizationService – adds it to DI container.
Just like scaffolding adds ApplicationDbContext to my controller, it actually injects it through the constructor, I will do the same with IAuthorizationService:
Now we can finally make use of the handler and requirement:
This is pretty simple. We call the authorization service and provide it with User, our resource – category and an instance of the requirement so it can figure out which handler it will run. Authorization service returns authorizationResult and we can tell by the Succeeded property if current User has access to this resource or not. If the user does not have access we return a ForbidResult.
This is really useful and nice, we can do this kind of check for any type of method and action. We can do this when we receive a DELETE request on our API, or EDIT/PUT, or any other action really.
Using attributes that make use of requirements and handlers
We will make use of TypeFilterAttribute.
If you look at OnActionExecutionAsync you can notice that we simply use first action argument, since we have a Create method and it only has one complex object, this is viable. If our action/method had a primitive and complex object we would probably need to pass in the index of our parameter (resource – Category) or type itself.
We can now make use of this attribute:
Our Create method looks much cleaner now!
This is just a demo, this example could definitely be done in another way. For example, you could make the attribute for InsertCategory without the need for generic requirementType attribute and specify it inside of the attribute itself.