Quantcast
Channel: WebAPI – leastprivilege.com
Viewing all articles
Browse latest Browse all 228

The State of Security in ASP.NET 5 and MVC 6: Authorization

$
0
0

The hardest part in designing an application is authorization. The requirements are always so app-specific that for 10 applications you often see 12 different implementations.

To make things worse, ASP.NET and MVC traditionally had not much more built-in to offer than boring role checks. This lead to either unmaintainable code (hard coded role names and Authorize attributes) or complete custom implementations – or both.

In ASP.NET 5, a brand new authorization API is supposed to improve that situation – and IMHO – oh yes it does. Let’s have a look.

Overview
ASP.NET 5 supports two styles of authorization out of the box – policy-based and resource-based.

Both styles are a substantial improvement over the current ASP.NET authorization features and reduce the need to write your own authorization attribute/filter/infrastructure – though this is still totally possible.

The new Authorize Attribute
My main gripe with the old attribute is that it pushes developers towards hard-coding roles (or even worse – names) into their controller code. It violates separation of concerns and leads to hard to maintain code with roles names sprinkled all over your code base.

Also – let’s face it – declarative, role-based security might be nice for demos but is nowhere near flexible enough to write  anything but trivial applications.

The new Authorize attribute can still do role checks like this:

[Authorize(Roles = "Sales")]
public IActionResult DoSalesyStuff()
{ /* .. */ }

But this is mainly for backwards compatibility (the ability to check for names is gone). The more recommended pattern is to use so called authorization policies instead:

[Authorize("SalesOnly")]
public IActionResult DoSalesyStuff()
{ /* .. */ }

Let’s have a look at policies next.

Policies & Requirements
Policies are a way to create re-usable authorization logic. Policies consist of one or more so called requirements. If all requirements of a policy are met, the authorization check is successful – otherwise it fails.

Policies are created using a policy builder, and the following snippet creates a very simple policy (aka “require authenticated users”) and sets that globally in MVC:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // only allow authenticated users
        var defaultPolicy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
 
        services.AddMvc(setup =>
        {
            setup.Filters.Add(new AuthorizeFilter(defaultPolicy));
        });
    }
}

There are more extension methods similar to RequireAuthenticatedUser – e.g. RequireClaim or RequireRole.

Another common use case are named policies which can be referenced like the above SalesOnly example (again in Startup.ConfigureServices):

services.AddAuthorization(options =>
{
    // inline policies
    options.AddPolicy("SalesOnly", policy =>
    {
        policy.RequireClaim("department""sales");
    });
    options.AddPolicy("SalesSenior", policy =>
    {
        policy.RequireClaim("department""sales");
        policy.RequireClaim("status""senior");
    });
});

You can also encapsulate requirements in classes, e.g.:

public class StatusRequirement : AuthorizationHandler<StatusRequirement>, IAuthorizationRequirement
{
    private readonly string _status;
    private readonly string _department;
 
    public StatusRequirement(string department, string status, bool isSupervisorAllowed = true)
    {
        _department = department;
        _status = status;
    }
 
    protected override void Handle(AuthorizationContext context, StatusRequirement requirement)
    {
        if (context.User.IsInRole("supervisor"))
        {
            context.Succeed(requirement);
            return;
        }
 
        if (context.User.HasClaim("department", _department) &&
            context.User.HasClaim("status", _status))
        {
            context.Succeed(requirement);
        }
    }
}

Which can then be added like this:

options.AddPolicy("DevInterns", policy =>
{
    policy.AddRequirements(new StatusRequirement("development""intern"));
 
    // ..or using an extension method
    //policy.RequireStatus("development", "intern");
});

Under the covers, the AddAuthorization extension method also puts an IAuthorizationService (or more specifically the DefaultAuthorizationService) into the DI container. This class can be used to programmatically evaluate policies (amongst other things – more on that later).

To make the authorization service available – simply add it to e.g. a controller constructor:

public class TestController : Controller
{
    private readonly IAuthorizationService _authz;
 
    public TestController(IAuthorizationService authz)
    {
        _authz = authz;
    }
}

..and then use it in an action:

public async Task<IActionResult> SalesSeniorImperative()
{
    if (await _authz.AuthorizeAsync(User, "SalesSenior"))
    {
        return View("success");
    }
 
    return new ChallengeResult();
}

Scott used a slightly different approach, but using the same underlying infrastructure.

Remark ChallengeResult can be used to trigger an “access denied” condition in MVC. The cookie middleware e.g. will translate that either into a redirect to a login page for anonymous users, or a redirect to an access denied page for authenticated users.

Remark 2 Since views in MVC 6 also support DI, you can inject the authorization service there as well. Some people like this approach to conditionally render UI elements.

@{
    ViewData["Title"] = "Home Page";
    
    @using Microsoft.AspNet.Authorization
    @inject IAuthorizationService _authz
}
 
@if (await _authz.AuthorizeAsync(User, "SalesOnly"))
{
    <div>
        <a href="test/salesOnly">Sales only</a>
    </div>
}

This is a nice way to centralize authorization policies and re-use them throughout the application.

The only thing I don’t like about this approach is, that it pushes you towards using the claims collection as the sole data source for authorization decisions. As we all know, claims describe the identity of the user, and are not a general purpose dumping ground for all sorts of data – e.g. permissions.

It would be nice if one could use the DI system of ASP.NET to make further data source accessible in custom requirement. I’ve opened an issue for that – we’ll see what happens.

Resource-based Authorization
This is a new approach for ASP.NET and is inspired by the resource/action based approach that we had in WIF before (which was ultimately inspired by XACML). We also like that approach a lot, but the problem with the WIF implementation (and also ours) was always that due to the lack of strong typing, the implementation became messy quickly (or at least you needed a certain amount of discipline to keep it clean over multiple iterations).

The idea is simple – you identify resources that your application is dealing with – e.g. customers, orders, products (yawn). Then you write a so called handler for each of these resources where you express the authorization requirements in code.

You use requirements to express whatever action is supposed to be applied to the resource, and conclude with a success/failure, e.g.:

public class CustomerAuthorizationHandler : 
    AuthorizationHandler<OperationAuthorizationRequirementCustomer>
{
    protected override void Handle(AuthorizationContext context, 
        OperationAuthorizationRequirement requirement, 
        Customer resource)
    {
        // implement authorization policy for customer resource
    }
}

Operation requirements are built-in and can be used to model simple string-based actions – but you can also write your own, or derive from OperationAuthorizationRequirement.

public static class CustomerOperations
{
    public static OperationAuthorizationRequirement Manage = 
        new OperationAuthorizationRequirement { Name = "Manage" };
    public static OperationAuthorizationRequirement SendMail =
        new OperationAuthorizationRequirement { Name = "SendMail" };
 
    public static OperationAuthorizationRequirement GiveDiscount(int amount)
    {
        return new DiscountOperationAuthorizationRequirement
        {
            Name = "GiveDiscount",
            Amount = amount
        };
    }
}

You then register the resource handler with the DI system and can access it imperatively from within your controllers (using the above mentioned authorization service):

public async Task<IActionResult> Discount(int amount)
{
    var customer = new Customer
    {
        Name = "Acme Corp",
        Region = "south",
        Fortune500 = true
    };
 
    if (await _authz.AuthorizeAsync(User, customer, 
        CustomerOperations.GiveDiscount(amount)))
    {
        return View("success");
    }
            
    return new ChallengeResult();
}

What I like about this approach is that the authorization policy has full strong typed access to the domain object it implements authorization for, as well as the principal. This is a huge improvement over the WIF API. It also makes it easy to unit test your controller without the authorization code – and even more importantly (at least for security guy) – it allows unit testing the authorization policy itself.

protected override void Handle(AuthorizationContext context, 
    OperationAuthorizationRequirement requirement, 
    Customer resource)
{
    // user must be in sales
    if (!context.User.HasClaim("department""sales"))
    {
        context.Fail();
        return;
    }
 
    // ...and responsible for customer region
    if (!context.User.HasClaim("region", resource.Region))
    {
        context.Fail();
        return;
    }
 
    // if customer is fortune 500 - sales rep must be senior
    if (resource.Fortune500)
    {
        if (!context.User.HasClaim("status""senior"))
        {
            context.Fail();
            return;
        }
    }
 
    if (requirement.Name == "GiveDiscount")
    {
        HandleDiscountOperation(context, requirement, resource);
        return;
    }
 
    context.Succeed(requirement);
}

In addition, the resource handler can make full use of the DI system. That means we can inject access to arbitrary data stores. A very common use case is to use some sort of database to query permission tables or similar. This makes it very flexible.

Imagine a permission service that queries your authorization using some backing store (the _permissions variable):

private void HandleDiscountOperation(
    AuthorizationContext context, 
    OperationAuthorizationRequirement requirement, 
    Customer resource)
{
    var discountOperation = requirement as DiscountOperationAuthorizationRequirement;
    var salesRep = context.User.FindFirst("sub").Value;
 
    var result = _permissions.IsDiscountAllowed(
        salesRep,
        resource.Id,
        discountOperation.Amount);
 
    if (result)
    {
        context.Succeed(requirement);
    }
    else
    {
        context.Fail();
    }
}

All in all – I am very pleased with this new API and it will be interesting to see how it performs in a real application.

The full source code of the sample can be found here and the repo for the authorization API is here (open an issue if you have feedback)

Have fun!


Filed under: .NET Security, ASP.NET, WebAPI

Viewing all articles
Browse latest Browse all 228

Trending Articles