Home ASP.NET Core 2.0 Authentication with local logins - Implementing custom authorization policies
Post
Cancel

ASP.NET Core 2.0 Authentication with local logins - Implementing custom authorization policies

This post is part of a series on ASP.NET Core 2.0 Authentication and I am going to talk about authorization policies, something different from what we've seen so far in the series, as most of posts are focusing in authorization.

I will show how to secure certain resources based on specific criteria on user's claims.

Application

Application is based on previous post,ASP.NET Core 2.0 Authentication with local logins - Implementing claims transformation, with a few additions.
This time, I am going to demonstrate how to enforce authorization policies on pages, on Razor MVC views, and finally mixing authorization handlers in order to decide if user should have access to the resource or not.

Aside from the familiar home, login and profile pages, I have added a few more. Namely, I have added a page that has an age requirement, so in order to have access to this page, a user must be older than the minimum age requirement. This example, demonstrates simply how to utilize a single authorization policy and enforce it to a specific page.

To be able to demonstrate how to use authorization policies in a Razor MVC View, I have also added a new page, which is going to be visible in the navbar only when the user meets its requirements.

Finally, I have also added a couple more pages to support a more complex example of using and mixing authorization handlers. In this case, I am going to secure access to a blog, with members having roles such as admin, author, moderator and anonymous. Members whose role is one of the first three, should be able to access the blog administration panel. Anonymous users should only be able to view the listed blog posts.
I have added one page that lists some blog posts and contains a button which opens the administration panel only to elevated roles; anonymous users will not be able to see it, thanks to view-based authorization. A member is also denied access if hers/his account is frozen (only for elevated roles as well).
This example here is to demonstrate how to mix various handlers to allow a given logged-in user to access the blog's admin panel.

Source code

You can find the code outlined in this article on Github.

Authorization Policies

Authorization is a step that comes after authentication. I usually think about those terms in the following way:

  • Authentication is the process that wants me to verify who I am.
    • Who are you?
  • Authorization is the process that wants me to verify if I am qualified to access a certain resource based on predefined policies.
    • Are you allowed to perform this action?

Usually we perform authorization based on user's claims. These claims might be in a cookie or in a JWT token or in a SAML token, it doesn't really matter what the transport/store type is, all these have something in common and that is that they carry a specified amount of claims associated with a user.

Based on those claims now, we as developers, have the ability to create features which are very precise and solve much more complex authorization requirements. In ASP.NET Core, this ability is granted by authorization policies.

An authorization policy comprises of three components:

  1. The requirement
  2. The handler
  3. The configured policy

Requirements

A requirement defines the business logic behind an authorization policy. It can be used by one or more policies and in reality it is just a plain class object which implements the empty IAuthorizationRequirement interface. You can define your own parameters, properties and data for that requirement, which will be used directly by a handler.
A requirement can have multiple handlers.

Handlers

A handler evaluates the requirement's properties/business logic against its context to determine if access is allowed or not, so it can delegate the process to another handler or terminate it on the spot. You can use a handler to work with a specific requirement, and that handler should inherit from AuthorizationHandler<TRequirement> abstract class. You'll need to override theHandleRequirementAsync method, which has the following definition:

Makes a decision if authorization is allowed based on a specific requirement.

From this we can understand that a handler plays a crucial role in deciding to provide access or not to a user. This method, except of theAuthorizationHandlerContext context that is passed, it takes a generic TRequirement, which is the requirement provided in the AuthorizationHandler generic parameter.

A handler can have two states

  1. Succeeded
  2. Failed

A success state means after validating against the requirement, user's claims were valid, thus, access is granted, based on that policy. In order to fulfill a requirement, a simple call to the AuthorizationHandlerContext.Succeed method must commence, passing the requirement as a parameter. More on this in code coming next.

The failed state is more complicated. As mentioned earlier, a call to the Succeed method is enough to fulfill a given requirement, but doing nothing essentially means the requirement failed. However, if we look at the AuthorizationHandlerContext, a Fail method exists. Well, even if it exists it should be used with caution and I will explain why shortly.

Imagine this; fairly often we have several handlers responsible to verify a requirement. As long as one of them calls the Succeed method, access is approved.
But, if any of them fails, thus calling the Fail method, access is denied, no matter how may of the other handlers succeeded. If nobody calls the Succeed method, then the requirement is going to fail, but this won't stop the process from moving to the next handler for that requirement.
That said, we can understand that leaving out the call to the Fail method, gives us the ability to introduce additional handlers in the future, without needing to alter any existing handler code.

Fail method should only be used if there is some form of absolute blocker that would render the rest of the handlers and essentially rest of the policy useless, so use this method with caution.

Configuring a policy

To setup an authorization policy, you just need to use the authorization middleware, which can be registered in the pipeline by the AddAuthorization method in ConfigureServices. You can register as many policies as you like, but remember to map them to the appropriate authentication scheme if needed.
If you have already set the DefaultPolicy to have one default authentication scheme, then all the other policies will derive from it.

Use the AddPolicy method to add new policies by name, which can be either delegates or classes (OOP approach).

Then you can use that name with the Authorize attribute, for instance, to provide sophisticated security over a resource.

Implementation

Let's start off with the implementation, first we need to add the authorization middleware into the pipeline, so in ConfigureServices call the AddAuthorization method. I will provide the default authentication scheme to be used for all policies to be CookieAuthenticationDefaults.AuthenticationScheme.

This sets up a default authorization policy, which requires the user to be authenticated. The default authorization scheme for this policy and for the next to come is "cookie", for cookie authorization.

Age Requirement

I want to create a new page where people under 18 would be denied access. Let's first create and secure this page. In HomeController I'm adding the following action.

I've decorated this action with the Authorize attribute, defining a policy. Now, when a user navigates to /protected route, authentication will kick in first, to determine if that user is indeed authenticated and if that is true, the authorization policy will run to determine if access should be granted. If user is denied access, the default access denied screen will show, else resource will be presented to the user.
The Policies.AgeRestriction is just a constant with the value"AgeRestriction".
Configuration is fetched from appsettings.json file, following is some part of that config file

Now that we have everything in place, it's time to add that policy to the list of authorization policies. To do that, I need to get back the Startup.cs class, to ConfigureServices. In AddAuthorization method we saw earlier, I will use the AuthorizationOptions parameter to call the AddPolicy method and register a new policy.

I mentioned earlier, that in order to make a new policy you need three things, a requirement, a handler and to configure the policy. I've done the latter, now I need the requirement and its handler.
I could do them separately, but I have the ability to combine them in one class, by inheriting from AuthorizationRequirement<TRequirement>and implementing the IAuthorizationRequirement interface.
That said, I created a single class and I named itAgeRestrictionRequirement,and got to inherit and implement from the aforementioned objects.

Based on the above, we have a handler that itself is the requirement, passing the actualrequirement in the constructor, which is the minimum age.
In HandleRequirementAsync, I am testing against that requirement, by first fetching the date of birth claim. Notice that if the dateOfBirth is non-existent (null), the method returns, which means the policy has failed, but this is a soft fail, compared to a hard bycalling the Fail method. If the test against the age is successful, then a call to the Succeed method commences. Notice that you need to pass the requirement along with this method.

View based authorization

There are sometimes that you want to hide specific UI elements. You might want these elements to be visible to certain users, ones that have the appropriate claims or fulfill a certain authorization policy. Of course, this is not a way to secure an application, rather its a way to provide a better UX for your users.

In order to hide and show elements in Razor, based on authorization policies, you need an instance of the IAuthorizationService in your views. You can either inject that service in your view or inject it in_ViewImports.cshtmlto make it globally accessible. In this demo, I've injected it in _ViewImports

Now, I want to show/hide a menu item in _Layout.cshtml, based on a policy. I will use theAuthorizationService and call its AuthorizeAsync method. This will tell me if the authorization is successful or failed for that user. If it is successful, then I will be able to show the menu item, else it stays hidden.

Checks if a user meets a specific authorization policy against the specified resource.

Returns aflag indicating whether policy evaluation has succeeded or failed. This valueis true when the user fulfills the policy, otherwise false.

This method is asynchronous, so be sure to await it. The returned value is of type AuthorizationResult, which has two public properties

It requires a ClaimsPrincipal user object and has various overloads for the rest of its parameters, though in my case, I prefer the overload that contains a policy name string as a third parameter. This overload validates the user against the specified policy.

Next step is to create that policy, so firstly, I will add that authorization policy in ConfigureServices.

The Policies.DomainRestriction is a constant with a value of"DomainRestriction".
I also fetch some configuration data fromappsettings.json.

This lists the allowed domains which are passed as a list in requirement's constructor.
Like in the previous example, I created a class which is both a handler and a requirement.
This requirement allows access only to users with email addresses which are in a specific domain, for example, this email address myemail@customDomain.com is not valid as the domain is not in the list of the allowed domains we saw earlier, but myemail@gmail.com is valid and the policy will succeed in this case.

This handler succeeds only when user's email address domain is listed in the allowed domains.

Complex authorization policies

Finally, I will talk about setting up complex authorization policies. You might want to use multiple handlers on a requirement, like in this example, I want to be able to allow or deny access to a user based on his/her role (admin, author, moderator, anonymous) and his/her account freeze status. I would like to evaluate role requirements in an OR basis, whereas the freeze status in an AND (like) basis. If user account is frozen, then the policy fails.
To recap, I have a blog and this blog has some members and each member has a role, which is admin, author or moderator. These roles have access to the admin panel. Anonymous users cannot access the admin panel at all. A member can have his/her account freeze, which means that particular individual will not have access to admin panel anymore.
I'd like to start from setting the requirements first, then I will go to handlers and finally, I will show the configuration.

I start first by defining a BlogAccessRequirement, which is an empty marker class that implements the IAuthorizationRequirement. Reason behind that is that I want to add a single requirement and have multiple handlers use that requirement, but I am not interested particularly in specific data.

Now, I will create a handler for each role (admin, author, moderator), in order to have independent business logic for each one. That is the recommended best practice, it is better to have handlers that conform with the single responsibility principle, doing one thing, like in this case different handlers for each role and for the account freeze status.

Then comes the handler for the author

And finally, the handler for the moderator role

All these are pretty much similar and simple handlers, they just verify if the user is in a role. If user is in role, the handler succeeds, else it fails soft. But, one handler failing does not mean the policy will fail overall, another might succeed and this will mean the policy as a whole will succeed as well, so if one of them succeeds, then the policy is successful.
Next up, is the account freeze handler, which checks if an account is frozen. This is slightly more complex handler.

I inject a service in this handler in order to be able to communicate with the data store and fetch the persisted blog members.
In order to verify if the account is frozen, I call the FindMemberByUserNameAsync,passing the authenticated user's username. If the account is not frozen, then the policy will fail softly, otherwise, the policy fails hard, preventing further handlers from executing for this requirement. Because I call the Fail method here, this policy will fail no matter what, even if a previous handler has succeeded. But why I left the policy to fail softly and not call the Succeed method? I did this because other handlers are running also for this requirement. If one of them succeeds, then it's fine. But if I called Succeed here, I would allow access to other roles as well, for example member's of Guest role. So, I leave the other handlers to handle access for specific roles, while this one is only failing hard in case the account is frozen.

For more info on services, repositories and database items, please look at the source code on Github.

Final piece in this puzzle, is the authorization policy configuration in ConfigureServices. I have to add that requirement in authorization options, but I also have to register all the Blog* handlers in DI container, as instances ofIAuthorizationHandler.

Securing actions remains the same as with previous examples, just use the Authorize attribute and specify the policy name to match the one registered.

Summary

In this post, we've gone through authorization policies in ASP.NET Core. We've seen what a requirement is, the handler's role and how to stitch them all together.

We've gone through some examples, starting from defining a simple requirement, which can be created by defining a new class that implements IAuthorizationRequirement and inhertis from AuthorizationHandler<T>, with T being the requirement class. In this simple case, the same class is the requirement and the handler.
For view-based authorization, you learned that you need to inject the IAuthorizationService in a view or the _ViewImports.cshtml view. Using the AuthorizeAsync method, you can hide/show UI elements based on user's identity.
Finally, you learned also how to use a single requirement in multiple handlers, by creating a requirement class that implements the IAuthorizationRequirement and having multiple handlers for different types of authorization. You should register all handlers in DI asIAuthorizationHandler.

Next up, I will talk about social logins.


This post is part of the ASP.NET Core 2.0 Authentication series.

  1. ASP.NET Core 2.0 Cookie Authentication - Local logins
  2. ASP.NET Core 2.0 Authentication with local logins - Implementing claims transformation
  3. ASP.NET Core 2.0 Authentication with local logins - Responding to backend changes
  4. ASP.NET Core 2.0 Authentication with local logins - Implementing custom authorization policies
  5. ASP.NET Core 2.1 Authentication with social logins
  6. ASP.NET Core 2.0 Authentication with social logins - Implementing a profile store
  7. ASP.NET Core 2.0 Authentication with Azure Active Directory
  8. ASP.NET Core 2.0 Authentication with Azure Active Directory B2C
  9. ASP.NET Core 2.0 Authentication, IdentityServer4 and Angular
This post is licensed under CC BY 4.0 by the author.

ASP.NET Core 2.0 Authentication with local logins - Responding to backend changes

Setup .NET Core 2.1 project and test with XUnit on Linux VM

Comments powered by Disqus.