Page 66 - MSDN Magazine, October 2017
P. 66
Figure 2 Checking Policies Programmatically
Using the authorization service in a view can help hide elements of the UI that shouldn’t be within the reach of the current user given the current context. Keep in mind, though, that simply hid- ing options in the view is not enough. You always have to enforce policies in the controller, as well.
Custom Requirements
The stock requirements basically cover claims, authentication and provide a general-purpose mechanism for customization based on assertions, but you can create custom requirements, too. A policy requirement is made of two elements: a requirement class that holds just data, and an authorization handler that validates the data against the user. Custom requirements extend your abil- ity to express specific policies. For example, let’s say you want to extend the ContentsEditor policy by adding the requirement that the user must have at least three years of experience. Here’s how you would do that:
public class ExperienceRequirement : IAuthorizationRequirement {
public int Years { get; private set; }
public ExperienceRequirement(int minimumYears) {
public class AdminController : Controller {
private IAuthorizationService _authorization;
public AdminController(IAuthorizationService authorizationService) {
_authorization = authorizationService; }
public async Task<IActionResult> Save(Article article) {
var allowed = await _authorization.AuthorizeAsync( User, "ContentsEditor"));
if (!allowed)
return new ForbiddenResult();
// Proceed with the method implementation
... }
}
The Authorize attribute allows you to set a policy declaratively, but policies can also be invoked programmatically right from an action method, as shown in Figure 2.
If the programmatic check of permissions fails, you might want to return a ForbiddenResult object. Another option is returning ChallengeResult. In ASP.NET Core 1.x, returning a challenge tells the authorization middleware to return a 401 status code, or redirect the user to a login page, depending on configuration. The redirect won’t happen in ASP.NET Core 2.0, however, and even in ASP.NET Core 1.x the challenge ends up in a ForbiddenResult if the user is already logged in. In the end, the best approach is to return ForbiddenResult if the permission check fails.
Note that you can even perform a programmatic check of the policies from within a Razor view, as shown in the code here:
@{
var authorized = await Authorization.AuthorizeAsync(
User, "ContentsEditor")) }
@if (!authorized) {
<div class="alert alert-error">
You’re not authorized to access this page.
</div> }
For this code to work, however, you must first inject the depen- dency on the authorization service, as follows:
@inject IAuthorizationService Authorization
Figure 3 Sample Authorization Handler
Years = minimumYears;
}
A requirement must have at least one authorization handler. A handler is of type AuthorizationHandler<T>, where T is the requirement type. Figure 3 illustrates a sample handler for the ExperienceRequirement type.
Custom requirements extend your ability to express specific policies.
The sample authorization handler reads the claims associated with the user and checks for a custom EditorSince claim. If that isn’t found, the handler returns without success. Success is returned only if the claim exists and contains an integer value not less than the specified number of years.
The custom claim is expected to be a piece of information linked in some way to the user—for example, a column in the Users table—saved to the authentication cookie. However, once you hold a reference to the user, you can always find the username from the claims and run a query against any database or external service to get hold of the years of experience and use the infor- mation in the handler. (I’ll admit that this example would be a bit more realistic had the EditorSince value held a DateTime, and calculated if a given number of years had passed since the user began as an Editor.)
An authorization handler calls the method Succeed, passing the current requirement to notify that the requirement has been successfully validated. If the requirement didn’t pass, the handler doesn’t need to do anything and can just return. However, if the
}
public class ExperienceHandler : AuthorizationHandler<ExperienceRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
ExperienceRequirement requirement) {
// Save User object to access claims
var user = context.User;
if (!user.HasClaim(c => c.Type == "EditorSince"))
return Task.CompletedTask;
var since = user.FindFirst("EditorSince").Value.ToInt(); if (since >= requirement.Years)
context.Succeed(requirement);
return Task.CompletedTask; }
}
62 msdn magazine
Cutting Edge