Authorize Resource Tag Helper for ASP.NET Core

Originally posted to: https://www.davepaquette.com/archive/2017/11/28/authorize-resource-tag-helper.aspx

In my previous blog post, I wrote an Authorize tag helper that made it simple to use role and policy based authorization in Razor Views. In this blog post, we will take this one step further and build a tag helper for resource-based authorization.

Resource-Based Authorization

Using the IAuthorizationService in ASP.NET Core, it is easy to implement an authorization strategy that depends not only on properties of the User but also depends on the resource being accessed. To learn how resource-based authorization works, take a look at the well written offical documentation.

Once you have defined your authorization handlers and setup any policies in Startup.ConfigureServices, applying resource-based authorization is a matter of calling one of two overloads of the AuthorizeAsync method on the IAuthorizationService.

Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
object resource,
string policyName);

Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
object resource,
IAuthorizationRequirement requirements);

One method takes in a policy name while the other takes in an IAuthorizationRequirement. The resulting AuthorizationResult has a Succeeded boolean that indicates whether or not the user meets the requirements for the specified policy. Using the IAuthorizationService in a controller is easy enough. Simply inject the service into the controller, call the method you want to call and then check the result.

 
public async Task<IActionResult> Edit(int id)
{
var document = _documentContext.Find(documentId);

var authorizationResult = await _authorizationService.AuthorizeAsync(User, Document, "EditDocument");

if (authorizationResult.Succeeded)
{
return View(document);
}
else
{
return new ChallengeResult();
}
}

Using this approach, we can easily restrict which users can edit specific documents as defined by our EditDocument policy. For example, we might limit editing to only users who originally created the document.

Where things start to get a little ugly is if we want to render a UI element based on resource-based authorization. For example, we might only want to render the edit button for a document if the current user is actually authorized to edit that document. Out of the box, this would require us to inject the IAuthorizationService in the Razor view and use it like we did in the controller action. The approach works, but the Razor code will get ugly really fast.

Authorize Resource Tag Helper

Similar to the Authorize Tag Helper from the last blog post, this Authorize Resource Tag Helper will make it easy to show or hide blocks of HTML by evaluating authorization rules.

Resource-Based Policy Authorization

Let’s assume we have a named “EditDocument” that requires a user to be the original author of a Document in order to edit the document. With the authorize resource tag helper, specify the resource instance using the asp-authorize-resource attribute and the policy name using the asp-policy attribute. Here is an example where Model is an instance of a Document

<a href="#" asp-authorize-resource="Model" 
asp-policy="EditDocument" class="glyphicon glyphicon-pencil"></a>

If the user meets the requirments for the “EditDocument” policy and the specified resource, then the block of HTML will be sent to the browser. If the requirements are not met, the tag helper will suppress the output of that block of HTML. The tag helper can be applied to any HTML element.

Resource-Based Requirement Authorization

Instead of specifying a policy name, authorization can be evaluated by specifying an instance of an IAuthorizationRequirement. When using requirements directly instead of policies, specify the requirement using the asp-requirement attribute.

<a href="#" asp-authorize-resource="document"
asp-requirement="Operations.Delete"
class="glyphicon glyphicon-trash text-danger">
</a>

If the user meets Operations.Delete requirement for the specified resource, then the block of HTML will be sent to the browser. If the requirement is not met, the tag helper will suppress the output of that block of HTML. The tag helper can be applied to any HTML element.

Implementation Details

The authorize resource tag helper itself is fairly simple. The implementation will likely evolve after this blog post so you can check out the latest version here.

The tag helper needs an instance of the IHttpContextAccessor to get access to the current user and an instance of the IAuthorizationService. These are injected into the constructor. In the ProcessAsync method, either the specified Policy or the specified Requirement are passed in to the IAuthorizationService along with the resource.

[HtmlTargetElement(Attributes = "asp-authorize-resource,asp-policy")]
[HtmlTargetElement(Attributes = "asp-authorize-resource,asp-requirement")]
public class AuthorizeResourceTagHelper : TagHelper
{
private readonly IAuthorizationService _authorizationService;
private readonly IHttpContextAccessor _httpContextAccessor;

public AuthorizeResourceTagHelper(IHttpContextAccessor httpContextAccessor, IAuthorizationService authorizationService)
{
_httpContextAccessor = httpContextAccessor;
_authorizationService = authorizationService;
}

/// <summary>
/// Gets or sets the policy name that determines access to the HTML block.
/// </summary>
[HtmlAttributeName("asp-policy")]
public string Policy { get; set; }

/// <summary>
/// Gets or sets a comma delimited list of roles that are allowed to access the HTML block.
/// </summary>
[HtmlAttributeName("asp-requirement")]
public IAuthorizationRequirement Requirement { get; set; }


/// <summary>
/// Gets or sets the resource to be authorized against a particular policy
/// </summary>
[HtmlAttributeName("asp-authorize-resource")]
public object Resource { get; set; }

public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (Resource == null)
{
throw new ArgumentException("Resource cannot be null");
}
if (string.IsNullOrWhiteSpace(Policy) && Requirement == null)
{
throw new ArgumentException("Either Policy or Requirement must be specified");

}
if (!string.IsNullOrWhiteSpace(Policy) && Requirement != null)
{
throw new ArgumentException("Policy and Requirement cannot be specified at the same time");
}

AuthorizationResult authorizeResult;

if (!string.IsNullOrWhiteSpace(Policy))
{
authorizeResult = await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, Resource, Policy);
}
else if (Requirement != null)
{
authorizeResult =
await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, Resource,
Requirement);
}
else
{
throw new ArgumentException("Either Policy or Requirement must be specified");
}

if (!authorizeResult.Succeeded)
{
output.SuppressOutput();
}
}

Note that either a policy or a requirement must be specified along with a resource, but you can’t specify both a policy AND a requirement. Most of the code in the ProcessAsync method is checking the argument values to make sure a valid combination was used.

Try it out

You can see the authorize resource tag helper in action on my tag helper samples site here. The sample site contains the examples listed in this blog post and also provides a way to log in as different users to test different scenarios.

The authorize resource tag helper is also available on NuGet so you can use it in your own ASP.NET Core application.

dotnet add package TagHelperSamples.Authorization

Let me know what you think. Would you like to see this tag helper including in the next release of ASP.NET Core?

NOTE: If you choose to use the authorize resource tag helper in your application, you should remember that hiding a section of HTML is not enough to fully secure your application. You also need to make sure that resource-based authorization is applied to any related controllers and action methods.

What’s Next?

There is one more authorization scenario related to supporting different authorization schemes that I hope to cover. Watch out for that in a future blog post. Also, this tag helper project is all open source so feel free to jump in on GitHub.

The Monsters Weekly - Episode 112 - Migrating Knockout to React

The AllReady project has grown over the years and in order to keep it fresh, we’d like to try moving from Knockout to React for our client-side components. In this episode, Simon takes us through replacing a small Knockout component with an equivalent React component

 

The Monsters Weekly - Episode 111 - Authorize Tag Helper

In this episode, Monster Dave builds a new tag helper that makes it easy to control access to any block HTML in a Razor view. Join us for a tour of the Authorize tag helper

Related Links:
Tag Helper Samples Reop - https://github.com/dpaquette/TagHelperSamples
GitHub Issue for MVC Core  - https://github.com/aspnet/Mvc/issues/3785
Blog Post Deep Dive - https://www.davepaquette.com/archive/2017/11/05/authorize-tag-helper.aspx

 

The Monsters Weekly - Episode 110 - Live Unit Testing

The new live unit testing capabilities of Visual Studio 2017 are a real timesaver. In this video, we take a poke around at them.

Authorize Tag Helper for ASP.NET Core

Originally posted to: http://www.davepaquette.com/archive/2017/11/05/authorize-tag-helper.aspx

In ASP.NET Core, it’s easy to control access to Controllers and Action Methods using the [Authorize] attribute. This attribute provides a simple way to ensure only authorized users are able to access certain parts of your application. While the [Authorize] attribute makes it easy to control authorization for an entire page, the mechanism for controlling access to a section of a page is a little clumsy, involving the use of a the IAuthorizationService and writing C# based if blocks in your Razor code.

In this blog post, we build an Authorize tag helper that makes it easy to control access to any block HTML in a Razor view.

Authorize Tag Helper

The basic idea of this tag helper is to provide similar functionality to the [Authorize] attribute and it’s associated action filter in ASP.NET Core MVC. The authorize tag helper will provide the same options as the [Authorize] attribute and the implementation will be based on the authorize filter. In the MVC framework, the [Authorize] attribute provides data such as the names of roles and policies while the authorize filter contains the logic to check for roles and policies as part of the request pipeline. Let’s walk through the most common scenarios.

Simple Authorization

In it’s simplest form, adding the [Authorize] attribute to a controller or action method will limit access to that controller or action method to users who are authenticated. That is, only users who are logged in will be able to access those controllers or action methods.

With the Authorize tag helper, we will implement a similar behaviour. Adding the asp-authorize attribute to any HTML element will ensure that only authenticated users can see that that block of HTML.

<div asp-authorize class="panel panel-default">
<div class="panel-heading">Welcome !!</div>
<div class="panel-body">
If you're logged in, you can see this section
</div>
</div>

If a user is not authenticated, the tag helper will suppress the output of that entire block of HTML. That section of HTML will not be sent to the browser.

Role Based Authorization

The [Authorize] attribute provides an option to specify the role that a user must belong to in order to access a controller or action method. For example, if a user must belong to the Admin role, we would add the [Authorize] attribute and specify the Roles property as follows:

[Authorize(Roles = "Admin")]
public class AdminController : Controller
{
//Action methods here
}

The equivalent using the Authorize tag helper would be to add the asp-authorize attribute to an HTML element and then also add the asp-roles attribute specifying the require role.

<div asp-authorize asp-roles="Admin" class="panel panel-default">
<div class="panel-heading">Admin Section</div>
<div class="panel-body">
Only admin users can see this section. Top secret admin things go here.
</div>
</div>

You can also specify a comma separated list of roles, in which case the HTML would be rendered if the user was a member of any of the roles specified.

Policy Based Authorization

The [Authorize] attribe also provides an option to authorize users based on the requirements specified in a Policy. You can learn more about the specifics of this approach by reading the offical docs on Claims-Based Authorization and Custom-Policy Based Authorization. Policy based authorization is applied by specifying Policy property for the [Authorize] attribute as follows:

[Authorize(Policy = "Seniors")]
public class AdminController : Controller
{
//action methods here
}

This assumes a policy named Seniors was defined at startup. For example:

services.AddAuthorization(o =>
{
o.AddPolicy("Seniors", p =>
{
p.RequireAssertion(context =>
{
return context.User.Claims
.Any(c => c.Type == "Age" && Int32.Parse(c.Value) >= 65);
});
});

}
);

The equivalent using the Authorize tag helper would be to add the asp-authorize attribute to an HTML element and then also add the asp-policy attribute specifying the policy name.

<div asp-authorize asp-policy="Seniors" class="panel panel-default">
<div class="panel-heading">Seniors Only</div>
<div class="panel-body">
Only users age 65 or older can see this section. Early bird dinner coupons go here.
</div>
</div>

Combining Role and Policy Based Authorization

You can combine the role based and policy based approaches by specifying both the asp-roles and asp-policy attributes. This has the effect of requiring that the user meets the requiremnts for both the role and the policy. For example, the following would require that the usere were both a member of the Admin role and meets the requirements defined in the Seniors policy.

<div asp-authorize asp-roles="Admin" asp-policy="Seniors" class="panel panel-default">
<div class="panel-heading">Admin Seniors Only</div>
<div class="panel-body">
Only users who have both the Admin role AND are age 65 or older can see this section.
</div>
</div>

Implementation Details

The Authorize tag helper itself is fairly simple. The implementation will likely evolve after this blog post so you can check out the latest version here.

The tag helper implements the IAuthorizeData interface. This is the interface implemented by the Authorize attribute in ASP.NET Core. In the ProcessAsync method, the properties of IAuthorizeData are used to create an effective policy that is then evaluated against the current HttpContext. If the policy does not succeed, then the output of the tag helper is supressed. Remember that supressing the output of a tag helper means that the HTML for that element, including it’s children, will be NOT sent to the client.

[HtmlTargetElement(Attributes = "asp-authorize")]
[HtmlTargetElement(Attributes = "asp-authorize,asp-policy")]
[HtmlTargetElement(Attributes = "asp-authorize,asp-roles")]
[HtmlTargetElement(Attributes = "asp-authorize,asp-authentication-schemes")]
public class AuthorizationPolicyTagHelper : TagHelper, IAuthorizeData
{
private readonly IAuthorizationPolicyProvider _policyProvider;
private readonly IPolicyEvaluator _policyEvaluator;
private readonly IHttpContextAccessor _httpContextAccessor;

public AuthorizationPolicyTagHelper(IHttpContextAccessor httpContextAccessor, IAuthorizationPolicyProvider policyProvider, IPolicyEvaluator policyEvaluator)
{
_httpContextAccessor = httpContextAccessor;
_policyProvider = policyProvider;
_policyEvaluator = policyEvaluator;
}

/// <summary>
/// Gets or sets the policy name that determines access to the HTML block.
/// </summary>
[HtmlAttributeName("asp-policy")]
public string Policy { get; set; }

/// <summary>
/// Gets or sets a comma delimited list of roles that are allowed to access the HTML block.
/// </summary>
[HtmlAttributeName("asp-roles")]
public string Roles { get; set; }

/// <summary>
/// Gets or sets a comma delimited list of schemes from which user information is constructed.
/// </summary>
[HtmlAttributeName("asp-authentication-schemes")]
public string AuthenticationSchemes { get; set; }

public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var policy = await AuthorizationPolicy.CombineAsync(_policyProvider, new[] { this });

var authenticateResult = await _policyEvaluator.AuthenticateAsync(policy, _httpContextAccessor.HttpContext);

var authorizeResult = await _policyEvaluator.AuthorizeAsync(policy, authenticateResult, _httpContextAccessor.HttpContext, null);

if (!authorizeResult.Succeeded)
{
output.SuppressOutput();
}
}
}

The code in the ProcessAsync method is based on the AuthorizeFilter from ASP.NET Core MVC.

Try it out

You can see the Authorize tag helper in action on my tag helper samples site here. The sample site contains the examples listed in this blog post and also provides a way to log in as different users to test different scenarios.

The Authorize tag helper is also available on NuGet so you can use it in your own ASP.NET Core application.

dotnet add package TagHelperSamples.Authorization

Let me know what you think. Would you like to see this tag helper included in the next release of ASP.NET Core?

What’s Next?

If you choose to use the Authorize tag helper in your application, you should remember that hiding a section of HTML is not enough to fully secure your application. You also need to make sure that authorization is applied to any related controllers and action methods. The Authorize tag helper is meant to be used in conjugtion with the [Authorize] attribute, not as a replacement for it.

There are a couple more scenarios I would like to go through and I will address those in a future post. One of those is supporting different Authorization Schemes and the other resource based authorization. Of course, this project is all open source so feel free to jump in on GitHub.

The Monsters Weekly - Episode 109 - DbContext Pooling in Entity Framework Core 2.0

Entity Framework Core 2 was released recently. In today’s episode we explore a new feature called DbContext Pooling. See how enabling DbContext Pooling might magically make your ASP.NET Core application faster!

Related Links:
GitHub Repo - https://github.com/AspNetMonsters/EP109-EFDbContextPooling
Netling Load Testing Tool - https://github.com/hallatore/Netling
.NET Conf Video - What’s new in EF Core 2.0 - https://channel9.msdn.com/Events/dotnetConf/2017/T221

                 (Section on DbContext Pooling starts at 10:55s)

 

The Monsters Weekly - Episode 108 - Using the LIKE operator in Entity Framework Core 2.0

Entity Framework Core 2 was released recently. In today’s episode we explore a new feature that allows us to specify the SQL Server LIKE operator when querying for entities.

Related Links:
GitHub Repo - https://github.com/AspNetMonsters/EP108-EFLikeOperator

The Monsters Weekly - Episode 107 - Azure Key Vault

In this episode we take a look at how to retrieve secrets from Azure Key Vault for use in your ASP.NET Core application

The Monsters Weekly - Episode 106 - Global Query Filters in Entity Framework Core 2.0

Entity Framework Core 2 was released recently. In today’s episode we explore a new feature called Global Query Filters. See how query filters can be applied globally to easily support features such as soft delete and even multi-tenancy.

Episode Sponsor:
AppVeyor - Continuous Delivery Services for Windows Developers

Related Links:
Code from the show on GiHub

 

The Monsters Weekly - Episode 105 - SQL Injection attacks in Entity Framework Core 2.0

Entity Framework Core 2 was released recently. In today’s episode we explore a new feature which automatically paramaterizes SQL Queries when the FromSql method is used with an interpolated string. Monster Dave shows us exactly why parametrized queries are so important when querying using raw sql.

Episode Sponsor:
AppVeyor - Continuous Delivery Services for Windows Developers

 

Related Links:
EF Core Injection Samples by Nick Craver
FormattableString - MSDN