78

I'm trying out some of the new stuff in VS2013 RC with MVC5 and the new OWIN authentication middleware.

So, I'm used to using the [Authorize] attribute to limit actions by role but I'm trying to use claims/activity based authorization, and I can't find an equivalent attribute for it.

Is there an obvious one I'm missing or do I need to roll my own? I kinda expected there to be one out of the box.

What I'm looking for specifically is something along the lines of [Authorize("ClaimType","ClaimValue")] I suppose.

Thanks in advance.

EightyOne Unite
  • 11,141
  • 12
  • 73
  • 102
  • 1
    just as a suggestion, please put the UPDATE section as a new answer, so that's clear for everyone that it's another approach (and not part of your question) – g3rv4 Feb 03 '15 at 13:20
  • I'd do that, but then I'd want to accept my own answer,..and that's just not what a gentleman does :-) – EightyOne Unite Feb 03 '15 at 13:30
  • 1
    I asked exactly that on meta and here's what they replied http://meta.stackexchange.com/questions/216719/should-i-edit-my-question-or-post-a-new-answer so there seems to be consensus ;) – g3rv4 Feb 03 '15 at 13:45
  • @Stimul8d I have to agree with Gervasio - questions are for questions, answers are for answers. You wouldn't have to mark it as accepted if you didn't want to; but it would make it clearer for other people. – dav_i Mar 02 '15 at 15:24
  • @Gervasio Fair enough,...done. – EightyOne Unite Mar 02 '15 at 19:38
  • 2
    I think it's brutal that this tech did not ship with the plumbing for attributes, as compared to the implementation for Roles. – Pittsburgh DBA May 04 '15 at 19:40

5 Answers5

75

I ended up just writing a simple attribute to handle it. I couldn't find anything in the framework right out of the box without a bunch of extra config. Listed below.

public class ClaimsAuthorizeAttribute : AuthorizeAttribute
{
    private string claimType;
    private string claimValue;
    public ClaimsAuthorizeAttribute(string type, string value)
    {
        this.claimType = type;
        this.claimValue = value;
    }
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        var user = filterContext.HttpContext.User as ClaimsPrincipal;
        if (user != null && user.HasClaim(claimType, claimValue))
        {
            base.OnAuthorization(filterContext);
        }
        else
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
    }
}

Of course, you could remove the type and value params if you were happy to use the controller-action-verb triplet for claims somehow.

Sudhanshu Mishra
  • 5,747
  • 2
  • 52
  • 68
EightyOne Unite
  • 11,141
  • 12
  • 73
  • 102
  • 6
    For forward compatibility it would be best to use `filterContext.HttpContext.user`. – Erik Philips Apr 16 '15 at 04:06
  • 1
    I ended up doing something almost identical, but it forces a role-style mentality in terms of applying the attributes. The separation of concerns espoused by @leastprivilege looks much stronger. – Pittsburgh DBA May 04 '15 at 19:41
  • 2
    @Stimul8d Thanks for the answer, this worked out perfectly in my MVC 5 app! It makes more sense to me to implement your own ClaimsAuthorizationAttribute then going with the ClaimsAuthorizationManager (too much configuration required) – landsteven Apr 20 '16 at 18:31
  • 1
    Good answer especially because even I was able to understand and incorporate it. – Vasily Hall Oct 30 '17 at 17:31
  • A good answer - I returned a HTTP 403 to prevent cycling back to the logon page. As for Roles, you can use them similar to claims. Instead of a single business role like ADMIN, use many roles such as CanSaveData. You'll want a custom table that maps the pseudo role ADMIN to all the roles it is a member of. Claims is the way to go thou as it's built-in – pixelda Jan 28 '18 at 20:54
30
  1. You wouldn't check for claims specifically, but rather for action/resource pairs. Factor out the actual claims / data checks into an authorization manager. Separation of concerns.
  2. MVC and ClaimsPrincipalPermission is not a good match. It throws a SecurityException and is not unit testing friendly.

My version is here: http://leastprivilege.com/2012/10/26/using-claims-based-authorization-in-mvc-and-web-api/

leastprivilege
  • 17,248
  • 1
  • 29
  • 47
  • 2
    Your version does not appear to be in the latest MVC 5 build? If so, which Nuget package contains it? – Brian Mains Mar 19 '14 at 14:17
  • 2
    http://www.nuget.org/packages/Thinktecture.IdentityModel.SystemWeb/ - and it is renamed to ResourceActionAuthorizeAttribute – leastprivilege Mar 20 '14 at 07:08
  • 4
    @leastprivilege I guess it would make sense to update your [blog post](http://leastprivilege.com/2012/10/26/using-claims-based-authorization-in-mvc-and-web-api/) with information about the renamed attribute and the available nuget packages for both MVC and Web API 2.x? I don't know how I should have known about the renaming if it wasn't for this comment section ;) – Lasse Christiansen Jun 01 '14 at 21:03
  • Thanks all. Needed this comment as well to help me with MVC5. – ACG Oct 07 '14 at 09:07
  • @leastprivilege I am using the above-referenced NuGet package, and it is not fully populating the Resource collection on the AuthorizationContext. This collection only ever contains one element, and it is not the resource passed as params into ResourceActionAuthorizeAttribute. Rather, it is the name of the controller method. Thoughts? – Pittsburgh DBA May 04 '15 at 20:50
  • 4
    I don't know if this answer is still up to date, but I still have no idea what a resource is or what it has to do with controller actions and the claims I have. Coming at this as someone who isn't an Owin expert, could you expand the answer out a little please? – Craig Brett May 15 '15 at 14:06
  • 1
    Using an attribute is still a separation of concern, as the logic is inside the attribute code, while keeping the attribute property close enough for the developer, so he will not forget it. – Softlion Jul 18 '16 at 10:01
9

I found that you can still use the Authorization attribute with roles and users, with claims.
For this to work, your ClaimsIdentity have to include 2 specific claim types:

    ClaimTypes.Name

and

    ClaimTypes.Role

Then in your class derived from OAuthAuthorizationServerProvider, in the GrantXX methods you use, when you create your ClaimsIdentity, add these 2 claims.

Example:

    var oAuthIdentity = new ClaimsIdentity(new[]
    {
        new Claim(ClaimTypes.Name, context.ClientId),
        new Claim(ClaimTypes.Role, "Admin"),
    }, OAuthDefaults.AuthenticationType);

Then on any action you can use [Authorize(Roles ="Admin")] to restrict access.

Softlion
  • 11,311
  • 10
  • 52
  • 78
  • This does the trick! What caught me out was not regenerating the token after making the changes - doh! Just to be clear, with the code above, [Authorize(Roles ="Admin")] is what you need to restrict access to a method. – Kinetic Aug 08 '17 at 12:57
4

In ASP.NET Core 3, you can configure security policies like this:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddAuthorization(options =>
    {
        options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
    });
}

then use AuthorizeAttribute to require the user meet the requirements of a specific policy (in other words, meet the claim backing that policy).

[Authorize(Policy = "EmployeeOnly")]
public IActionResult VacationBalance()
{
    return View();
}

Source.

Eric J.
  • 139,555
  • 58
  • 313
  • 529
3
[ClaimsPrincipalPermission(SecurityAction.Demand, Operation="Delete", Resource="Customer")]
public ActionResult Delete(int id)
{
    _customer.Delete(id);
    return RedirectToAction("CustomerList");
}

ClaimsPrincipalPermissionAttribute Class

jd4u
  • 5,599
  • 2
  • 25
  • 28
  • 2
    It's seems about right, but it's a lot of leg work and extra references for something that's supposed to be baked in. I've accepted the answer but check my edit. – EightyOne Unite Oct 15 '13 at 12:14
  • 1
    Doesn't this throw an exception, instead of returning an appropriate HTTP response? – Ronnie Overby Oct 19 '13 at 06:11