Headline: Here is what I use for authorizing controller and actions in an MVC application that uses the new Claims based authorization.
Some Great Resources
Before I even begin, let me point people to some great resources I found today on this topic. The first has so many posts on the topic and they are well written. Check out the Claims based security section:
http://dotnetcodr.com/security-and-cryptography/ by Andras Nemes (Stockholm, Sweden)
The second has quite a few resources too.
http://leastprivilege.com/2012/10/26/using-claims-based-authorization-in-mvc-and-web-api/ by Dominick Baier (Heidelberg, Germany)
Motivation
I spent lots of time looking at claims based security as the new WIF (Windows Identity Foundation) 4.5 has rolled out.
There is an attribute called ClaimsPrincipalPermission that I thought would do the trick. However, I’m not impressed. I’d love to delete this post if someone can show me what I’m doing wrong.
So the usage would be something like:
[ClaimsPrincipalPermission(SecurityAction.Demand, Resource = “Salary”, Operation = “Edit”)]
Now, suppose you have a claims system where you have addresses and salaries as resources. And some people can view both, but only edit the address.
I thought that you had name/value pairs like you do with the System.Security.Claims.Claim which has Type and Value. But it seems that if you add Resources of Address and Salary, and Operations of View and Edit there is no pairing.
So while I might give someone 1) Address/Edit, and Salary/View expecting them to not be able to edit Salary information it will let them.
What I’ve seen is it work this way: Look in all the Resources – do they have Salary? Good, now look in all the Operations – do they have Edit? Great, let them do it!
That’s not what I expected, wanted, and frankly I’ve not yet come up with a scenario where I think it would be useful.
ClaimAuthorize
So what I did instead was create a new attribute called ClaimAuthorizeAttribute.
So now if I have a ClaimIdentity with Claims of new Claim(“Address”, “Edit”) and new Claim(“Salary”, “View”) and place this on the action or controller:
[ClaimAuthorize(ClaimType=”Salary”, ClaimValue=”Edit”)]
The user would not get through.
So here is the code for that. If someone really wants it, I also have all the unit tests for it.
- /// <summary>
- /// Specifies that access to a controller or action method is restricted
- /// to users who meet the claims requirement.
- /// </summary>
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
- public class ClaimAuthorizeAttribute : FilterAttribute, IAuthorizationFilter
- {
- /// <summary>
- /// Called when a process requests authorization.
- /// </summary>
- /// <param name="filterContext">The filter context, which encapsulates for information
- /// using Ccr.Web.Mvc.ClaimAuthorizeAttribute.</param>
- /// <exception cref="System.ArgumentNullException">The filterContext is null.</exception>
- public void OnAuthorization(AuthorizationContext filterContext)
- {
- if (ReferenceEquals(filterContext, null))
- throw new ArgumentNullException("filterContext");
- if (!IsAuthorized(filterContext.HttpContext, ClaimType, ClaimValue))
- HandleUnauthorizedRequest(filterContext);
- }
- // Optional parameters to make unit testing easier.
- internal static bool IsAuthorized(HttpContextBase httpContext, string claimType = "", string claimValue = "")
- {
- if (ReferenceEquals(httpContext, null))
- throw new ArgumentNullException("httpContext");
- var user = httpContext.User;
- if (!user.Identity.IsAuthenticated)
- return false;
- var claimsPrincipal = user as ClaimsPrincipal;
- if (ReferenceEquals(claimsPrincipal, null))
- return false;
- if (claimsPrincipal.HasClaim(claimType, claimValue))
- return true;
- return false;
- }
- /// <summary>
- /// Gets or sets the claim type required for access to the controller or action.
- /// </summary>
- public string ClaimType { get; set; }
- /// <summary>
- /// Optionally gets or sets the claim value required for access to the controller or action.
- /// If not specified, then "True" will be used. The convention for most claims is that the
- /// value is set to True.
- /// </summary>
- private string _claimValue = true.ToString(CultureInfo.InvariantCulture);
- public string ClaimValue
- {
- get { return _claimValue; }
- set { _claimValue = value; }
- }
- /// <summary>
- /// Processes HTTP requests that fail authorization.
- /// </summary>
- /// <param name="filterContext">Encapsulates the information for using Ccr.Web.Mvc.ClaimAuthorizeAttribute.
- /// The filterContext object contains the controller, HTTP context, reqest context,
- /// action result, and route data.</param>
- protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext)
- {
- if (ReferenceEquals(filterContext, null))
- throw new ArgumentNullException("filterContext");
- filterContext.Result = new HttpUnauthorizedResult();
- }
- }
If someone sees something I’m doing with the ClaimsPrincipalPermission attribute, please let me know.
Thanks – Karl
Leave a Reply