Click here to Skip to main content
13,704,811 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

12.2K views
7 bookmarked
Posted 8 Sep 2017
Licenced CPOL

ASP.NET Core 2.0 Authorization

, 8 Sep 2017
Rate this:
Please Sign up or sign in to vote.
How to implement authorization in ASP.NET Core. Continue reading...

Problem

How to implement authorization in ASP.NET Core.

Solution

Starting from the previous project on authentication, set up some dummy users and their claims:

if (username.ToLower() == "free")
            {
                return new List<Claim>
                {
                    new Claim(ClaimTypes.Name, "Free Member"),
                    new Claim("MembershipId", "111"),
                };
            }
            if (username.ToLower() == "paid")
            {
                return new List<Claim>
                {
                    new Claim(ClaimTypes.Name, "Paid Member"),
                    new Claim("MembershipId", "222"),
                    new Claim("HasCreditCard", "Y"),
                    new Claim(ClaimTypes.DateOfBirth, "01/01/2000"),
                    new Claim("AllowNewReleases", "true")
                };
            }
            if (username.ToLower() == "over18")
            {
                return new List<Claim>
                {
                    new Claim(ClaimTypes.Name, "Over 18"),
                    new Claim("MembershipId", "333"),
                    new Claim("HasCreditCard", "Y"),
                    new Claim(ClaimTypes.DateOfBirth, "01/01/1980"),
                    new Claim("AllowNewReleases", "false")
                };
            }
            return new List<Claim>
            {
                new Claim(ClaimTypes.Name, "Guest")
            };

We’re creating the following users with permissions as below:

UserMemberFree MemberPaid MemberOver 18New Release
Guest     
FreeYY   
PaidY Y Y
Over18Y YY 

In Startup, we’ll configure authorization policies, which will use claims to fulfil policies, i.e., authorize users:

services.AddAuthorization(options =>
            {
                options.AddPolicy("Authenticated", 
                    policy => policy.RequireAuthenticatedUser());

                options.AddPolicy("Member",
                    policy => policy.RequireClaim("MembershipId"));

                options.AddPolicy("PaidMember",
                    policy => policy.RequireClaim("HasCreditCard", "Y"));

                options.AddPolicy("Over18",
                    policy => policy.Requirements.Add(new AgeRequirement(18)));

                options.AddPolicy("CanRentNewRelease",
                    policy => policy.Requirements.Add(
                                  new RentNewReleaseRequirement()));
            });

Some of these simply check the existence of a claim and its value, however, the last two require some special business logic. Framework provides IAuthorizationRequirement and AuthorizationHandler for this purpose:

public class AgeRequirement : IAuthorizationRequirement
    {
        public AgeRequirement(int age)
        {
            this.Age = age;
        }

        public int Age { get; }
    }
    public class AgeRequirementHandler : AuthorizationHandler<AgeRequirement>
    {
        protected override Task HandleRequirementAsync(
            AuthorizationHandlerContext context, AgeRequirement requirement)
        {
            if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
                return Task.CompletedTask;

            var dateOfBirth = DateTime.Parse(context.User.FindFirst(
                c => c.Type == ClaimTypes.DateOfBirth).Value);

            int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
            if (calculatedAge > requirement.Age)
                context.Succeed(requirement);

            return Task.CompletedTask;
        }
    }

Add a controller to apply the authorization policies to:

[Authorize(Policy = "PaidMember")]
    public class RentalsController : Controller
    {
        private readonly IAuthorizationService authService;

        public RentalsController(IAuthorizationService authService)
        {
            this.authService = authService;
        }

        public IActionResult Rent()
        {
            return View();
        }

        [Authorize(Policy = "Over18")]
        public IActionResult RentOver18()
        {
            return View();
        }

        public async Task<IActionResult> RentNewRelease(
            Rental inputModel)
        {
            var result = await authService.AuthorizeAsync(
                                 User, inputModel, "CanRentNewRelease");
            if (!result.Succeeded)
                return Challenge();

            return View();
        }    
    }

Discussion

Authorization is about deciding permissions users have and resources they can access. In ASP.NET Core, this is achieved by first assigning claims to the user and then based on those claims, defining policies to determine user permissions.

Claims

Claim is just a name/value pair. During the authentication process, you assign them to ClaimsIdentity, which in turn is assigned to ClaimsPrincipal. Claims contain information about the user, e.g., their email, birth date, driving license number, passport number, etc.

Key to remember about claim is that they are what the user “is” and not what the user can do.

Policy

Policy is about what the user is allowed to do, it’s the permission rule. Policies are added when configuring authorization services in Startup class. Some rules are based on existence of claims while others on more complicated business logic.

In the code above, being a member and paid member, simply check the existence of a certain claim. For more complicated rules, you create a requirement and a handler for it. In the code above, age and new release rentals are examples of this (full code listing on GitHub).

Note: When multiple policies are applied on a controller/action, they form AND logical operation. In the code above, to access Over18 content (policy on action), you have to be a Paid Member too (policy on controller).

Requirements and Handlers

Requirement is a data class implementing a marker interface IAuthorizationRequirement. Requirement can have one or more handlers; classes inheriting from AuthorizationHandler type.

Handlers can also be resource based, i.e., take in a custom type. This is useful when authorization is based on data dynamically coming from database. For instance, when a movie is loaded from database, it will have a flag to indicate whether it’s a new release or not. Our requirement handler can use this resource (Rental object) to check against user claims:

public class RentNewReleaseRequirementHandler :
        AuthorizationHandler<RentNewReleaseRequirement, Rental>
    {
        protected override Task HandleRequirementAsync(
            AuthorizationHandlerContext context, 
            RentNewReleaseRequirement requirement, 
            Rental resource)
        {
            if (!context.User.HasClaim(c => c.Type == "AllowNewReleases"))
                return Task.CompletedTask;

            var allowNewReleases = bool.Parse(
                  context.User.FindFirst("AllowNewReleases").Value);

            if (resource.IsNewRelease && allowNewReleases)
                context.Succeed(requirement);

            return Task.CompletedTask;
        }
    }

Now we can inject IAuthorizationService in our controller and use it to check this policy, passing in our model:

public async Task<IActionResult> RentNewRelease(
            Rental inputModel)
        {
            var result = await authService.AuthorizeAsync(
                             User, inputModel, "CanRentNewRelease");
            if (!result.Succeeded)
                return Challenge();

            return View();
        }

Views

Injecting IAuthorizationService into views allows us to control UI elements using the policies:

@if ((await authService.AuthorizeAsync(
                         User, null, "PaidMember")).Succeeded)
                {
                    <a asp-controller="Rentals" asp-action="Rent">Rent</a>
                }
                else
                {
                    <span>Rent (unavailable)</span>
                }

Note: Showing and hiding of UI elements is not a replacement of [Authorize] attribute or using IAuthorizationService in controllers, users could access actions via URL!?

Migrating from ASP.NET Core 1.1

When converting the sample from ASP.NET Core 1.1 to 2.0, I had to make one simple change. The AuthorizeAsync() method on IAuthorizationService returns AuthorizationResult in 2.0 whereas in 1.1, it returned a bool. I had this code in 1.1 and it worked:

public async Task<IActionResult> RentNewRelease(
            Rental inputModel)
        {
            if (!await authService.AuthorizeAsync(
                   User, inputModel, "CanRentNewRelease"))
                return Challenge();

            return View();
        }

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Tahir Naushad
Software Developer (Senior)
United Kingdom United Kingdom
Qualified and skilled professional with experience working as a Software Developer, Technical Lead and Architect. I have worked on windows, web and distributed applications using the latest set of technologies within the Microsoft .NET ecosystem. I have lead teams using agile methodologies and trained developers in writing well-designed and maintainable software applications.

Currently focusing on C#, ASP.NET Core, EF, JavaScript, React, Azure, Domain Driven Design and Microservices.

You may also be interested in...

Comments and Discussions

 
PraiseGreat article helped me a lot Pin
Member 1294873815-Sep-17 3:30
memberMember 1294873815-Sep-17 3:30 
GeneralRe: Great article helped me a lot Pin
Tahir Naushad16-Sep-17 10:23
memberTahir Naushad16-Sep-17 10:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web05-2016 | 2.8.180920.1 | Last Updated 9 Sep 2017
Article Copyright 2017 by Tahir Naushad
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid