Authorization is hard – much harder than authentication because it is so application specific. Microsoft went through several iterations of authorization plumbing in .NET, e.g. PrincipalPermission, IsInRole, Authorization configuration element and AuthorizeAttribute. All of the above are horrible approaches and bad style since they encourage you to mix business and authorization logic (aka role names inside your business code).
WIF’s ClaimsPrincipalPermission and ClaimsAuthorizationManager tried to provide better separation of concerns – while this was a step in the right direction, the implementation was “sub-optimal” – based on a CLR permission attribute, exception based, no async, bad for unit testing etc…
In the past Brock and me worked on more modern versions that integrate nicer with frameworks like Web API and MVC, but with the advent of OWIN/Katana there was a chance to start over…
Resource Authorization Manager & Context
We are mimicking the WIF resource/action based authorization approach – which proved to be general enough to build your own logic on top. We removed the dependency on System.IdentityModel and made the interface async (since you probably will need to do I/O at some point). This is the place where you will centralize your authorization policy:
public interface IResourceAuthorizationManager
{
Task<bool> CheckAccessAsync(ResourceAuthorizationContext context);
}
(there is also a ResourceAuthorizationManager base class with some easy to use helpers for returning true/false and evaluations)
The context allows you to describe the actions and resources as lists of claims:
public class ResourceAuthorizationContext
{
public IEnumerable<Claim> Action { get; set; }
public IEnumerable<Claim> Resource { get; set; }
public ClaimsPrincipal Principal { get; set; }
}
Middleware
The corresponding middleware makes the authorization manager available in the OWIN enviroment:
public void Configuration(IAppBuilder app)
{
var cookie = new CookieAuthenticationOptions
{
AuthenticationType = "Cookie",
ExpireTimeSpan = TimeSpan.FromMinutes(20),
LoginPath = new PathString("/Login"),
};
app.UseCookieAuthentication(cookie);
app.UseResourceAuthorization(new ChinookAuthorization());
}
Usage
Since the authorization manager is now available from the environment (key: idm:resourceAuthorizationManager) you can get ahold of it from anywhere in the pipeline, construct the context and call the CheckAccessAsync method.
The Web API and MVC integration packages provide a ResourceAuthorize attribute for declarative checks:
[ResourceAuthorize(ChinookResources.AlbumActions.View, ChinookResources.Album)]
And several extension methods for HttpContextBase and HttpRequestMessage, e.g.:
if (!HttpContext.CheckAccess(
ChinookResources.AlbumActions.Edit,
ChinookResources.Album,
id.ToString()))
{
return new HttpUnauthorizedResult();
}
or..
var result = Request.CheckAccess(
ChinookResources.AlbumActions.Edit,
ChinookResources.Album,
id.ToString());
Testing authorization policy
Separating authorization policy from controllers and business logic is a good thing, centralizing the policy into a single place also has the nice benefit that you can now write unit tests against your authorization rules, e.g.:
[TestMethod]
public void Authenticated_Admin_Can_Edit_Album()
{
var ctx = new ResourceAuthorizationContext(User("test", "Admin"),
ChinookResources.AlbumActions.Edit,
ChinookResources.Album);
Assert.IsTrue(subject.CheckAccessAsync(ctx).Result);
}
or…
[TestMethod]
public void Authenticated_Manager_Cannot_Edit_Track()
{
var ctx = new ResourceAuthorizationContext(User("test", "Manager"),
ChinookResources.TrackActions.Edit,
ChinookResources.Track);
Assert.IsFalse(subject.CheckAccessAsync(ctx).Result);
}
Code, Samples, Nuget
The authorization manager, context, middleware and integration packages are part of Thinktecture.IdentityModel – see here.
The corresponding Nuget packages are:
..and here’s a sample using MVC (if anyone wants to add a Web API to it – send me a PR).
Filed under:
ASP.NET,
IdentityModel,
Katana,
OWIN,
WebAPI