asp.net core series 51 Identity authorization (Part 2)

1.6 Resource-based authorization

In the previous two articles, we became familiar with the five authorization methods (the policy authorization mentioned in the previous article and the custom authorization policy provider of IAuthorizationPolicyProvider were not discussed, and will be added later). The authorization method mentioned in this article is not a new authorization method, but a flexible control of authorization application scenarios.

Resource-based authorization is controlled in the razor pages handler or mvc action. Resources: For example, if an article is published by an author, only the author can update the article. Before the article can be evaluated for authorization, the article must be retrieved from the data store.

(1) Reference the IAuthorizationService authorization service

Authorization is a Startup class that implements the IAuthorizationService service and registers it with the service collection. The interface is referenced in the mvc action below to prepare for authorization control.

 public class DocumentController : Controller
    {

        private readonly IAuthorizationService _authorizationService;
        private readonly IDocumentRepository _documentRepository;

        public DocumentController(IAuthorizationService authorizationService,
                                  IDocumentRepository documentRepository)
        {
            _authorizationService = authorizationService;
            _documentRepository = documentRepository;
        }
    }

The IAuthorizationService interface has two AuthorizeAsync method overloads:

 //Overload 1: Specify resource resource and policy requirements list
         Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements);
        //Overload 2: Specify resource resource and policy name
        Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName);

(2) Definition of Authorization Requirements

Based on CRUD (create, read, update, delete) authorization operations, use the OperationAuthorizationRequirement helper class to provide some authorization names.

 /// <summary>
    ///Authorize four requirements Crud
    /// </summary>
    public static class Operations
    {
        public static OperationAuthorizationRequirement Create =
            new OperationAuthorizationRequirement { Name = nameof(Create) };
        public static OperationAuthorizationRequirement Read =
            new OperationAuthorizationRequirement { Name = nameof(Read) };
        public static OperationAuthorizationRequirement Update =
            new OperationAuthorizationRequirement { Name = nameof(Update) };
        public static OperationAuthorizationRequirement Delete =
            new OperationAuthorizationRequirement { Name = nameof(Delete) };
    } 

(3) Define handlers

 /// <summary>
    /// Interface AuthorizationHandler<TRequirement, TResource>
    /// Use OperationAuthorizationRequirement requirements and Document resources
    /// </summary>
    public class DocumentAuthorizationCrudHandler: AuthorizationHandler<OperationAuthorizationRequirement, Document>
    {

        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                       OperationAuthorizationRequirementrequirement,
                                                       Document resource)
        {
            //The current logged-in user is the author of the article and has read permission. In actual development, read TResource resources and requirement requirements from the database (requirements here are CRUD permissions)
            //During dynamic acquisition, it can be based on the user claim table UserClaim, or based on the role claim table RoleClaim, using context.User.HasClaim to judge
            if (context.User.Identity?.Name == resource.Author & amp; & amp;
                requirement.Name == Operations.Read.Name)
            {
                context.Succeed(requirement);
            }

            return Task.CompletedTask;
        }
    } 

(4) Use AuthorizeAsync to verify authorization in Action

When the user logs in and wants to access the article page (/Document/index/1), use the AuthorizeAsync method to make a call to determine whether the current user is allowed to view the provided article.

 /// <summary>
        /// /Document/index/1
        /// </summary>
        /// <param name="documentId"></param>
        /// <returns></returns>
        public async Task<IActionResult> Index(int documentId)
        {
            Document Document = _documentRepository. Find(documentId);

            if (Document == null)
            {
                return new NotFoundResult();
            }

            //Use the AuthorizeAsync overload method (1) to verify the user's access to resources. The condition is that the current user must be [email protected] because it is the user's article
            var authorizationResult = await _authorizationService. AuthorizeAsync(User, Document, Operations. Read);

            //If the authorization is successful, return to the page for viewing the document
            if (authorizationResult. Succeeded)
            {
                return View();
            }
            // User is authenticated, but authorization failed
            else if (User. Identity. IsAuthenticated)
            {
                return new ForbidResult();
            }
            else
            {
                //Challenge: Doubt, return to re-execute identity authentication, redirect to login page
                return new ChallengeResult();
            }
        }

?(5) Definition of Document entity and storage of this entity

 public class Document
    {
        public string Author { get; set; }

        public byte[] Content { get; set; }

        public int ID { get; set; }

        public string Title { get; set; }
    }

    public class DocumentRepository : IDocumentRepository
    {
        public Document Find(int documentId)
        {
            return new Document
            {
                Author = "[email protected]",
                Content = null,
                ID = documentId,
                Title = "Test Document"
            };
        }
    }

    public interface IDocumentRepository
    {
        Document Find(int documentId);
    } 

?(6) Add routing rules and inject IAuthorizationService service

 services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
 Routes.MapRoute(
                 name: "document",
                 template: "{controller=Document}/{action=Index}/{documentId?}");

Finally, after the user [email protected] logs in successfully, he accesses Document/index/1 and views the article successfully.

Summary: Resource-based authorization is applied in MVC actions or razor pages handlers. It is different from the previous authorization methods, because the authorization mentioned before is: authorizing files or folders when starting the program, in the controller action And apply the [Authorize] attribute on top of PageModel.

For the use cases of AuthorizeAsync overload method (2), please check the official website documentation, which will not be introduced here.

Thinking: In actual development projects, when processing resources such as (add, delete, modify, check) permissions, you can consider the resource-based authorization in this article, but the above example needs to be improved, because the handler defined in the example only targets Document resources. , and the requirements (referring to permissions) are hard-coded in the handler. If you want to implement universal resource authorization, resource and requirement permissions need to be obtained from the database. For example, consider the following modification:

 //Define a general TResource
         public class AuthorizationResource
           {
              public string UrlResource{get;set;}
           }
 //Modify in the action of index
         .AuthorizeAsync(User, new AuthorizationResource (){UrlResource="/Document/index/1" }, Operations.Read);
 //Handler modification, omitting authorization logic processing (database acquisition requirements and resources)
         public class DocumentAuthorizationCrudHandler: AuthorizationHandler<OperationAuthorizationRequirement, AuthorizationResource >

1.7 View-based authorization 

In project development, authorization permissions also need to control the page and display or hide the html of the page. Authorization service dependency injection needs to be used on the page. To inject the authorization service into the Razor view, use the @inject directive. If you want each view to use the authorization service, you need to insert the @inject directive into the file view of _ViewImports.cshtml. The following view authorization controls are resource-based authorization.

 @using Microsoft.AspNetCore.Authorization
    @inject IAuthorizationService AuthorizationService
 <!-- Specify the policy name !-->
?@if ((await AuthorizationService.AuthorizeAsync(User, "PolicyName")).Succeeded)
{
     <p>This paragraph is displayed because you fulfilled PolicyName.</p>
}
 <!-- Model refers to TResource !-->
@if ((await AuthorizationService.AuthorizeAsync(User, Model, Operations.Edit)).Succeeded)
{
    ?<p><a class="btn btn-default" role="button"
       href="@Url.Action("Edit", "Document", new { id = Model.Id })">Edit</a></p>
}

Summary: Authorization control in the view cannot guarantee permission security, and authorization services need to be implemented in the action. Open source Github

references

Resource-based authorization

View-based authorization