I'm looking for recommendations on how to have multiple authorize attributes on an action.
eg:
[AuthorizePermission(PermissionName.SectionOne, PermissionLevel.Two)]
[AuthorizePermission(PermissionName.SectionTwo, PermissionLevel.Three)]
public ActionResult Index(int userId = 0){
}
If the user has access to SectionOne OR SectionTwo with the required PermissionLevel then they should be allowed in.
The problem i'm facing is how do I check both attributes before deciding they aren't allowed in (as they are separate attributes)? If the first one fails then it will never get to the second one.
I can not pass both permission sets to one attribute as they need to be paired together.
Does anyone have any suggestions?
I can not pass both permission sets to one attribute as they need to be paired together.
Yes, you can.
There is no reason why you can't include all the permissions in a single attribute. Something like this:
[AuthorizePermission(new Permission[]{
new Permission(PermissionName.SectionOne, PermissionLevel.Two),
new Permission(PermissionName.SectionTwo, PermissionLevel.Three)}]
This would pass an array of Permission objects, which you can then evaluate in your method with OR logic.
public class AuthorizePermissionAttribute : AuthorizeAttribute
{
private Permission[] _permissions = null;
public AuthorizePermissionAttribute(Permission[] permissions)
{
_permissions = permissions;
}
}
You could even get fancy and add a parameter that tells whether to AND or OR them...
The only way that I know is something like this
public class CustomRolesAttribute : AuthorizeAttribute
{
public CustomRolesAttribute(params string[] roles)
{
Roles = String.Join(",", roles);
}
}
Usage:
[CustomRoles("members", "admin")]
Related
We have an asp.net mvc application which I'm porting to aspnet core mvc.
In the old solution authentication is done using Windows authentication.
On top of that we have an "activity based authentication" (like http://ryankirkman.com/2013/01/31/activity-based-authorization.html); a user is connected to roles and the roles are connected to rights. The users roles and corresponding rights is stored in a separate application that serves as authorization service for our application and handful of other systems.
A query to the authorization service api for the rights of user "Jon Doe" would get a response like this:
{
Email:"Jon.Doe#acme.com",
FirstName:"Jon",
LastName:"Doe",
Resources:
[
"CanAccessWebApplication",
"CanCopyAppointment",
"CanEditAppointment",
"CanEditContact",
"CanSaveContact"
...
]
Alias:"1234567",
UserId:"1234"
}
In our current application these rights are checked using attributes (that we have implemented our selves) on the controller methods:
public ContactController
{
[ActionUserAccess("CanSaveContact")]
public ActionResult SaveContact
{
...
}
}
The current legacy implementation of the ActionUserAccessAttribute filter looks like this:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public sealed class ActionUserAccessAttribute : ActionFilterAttribute
{
private readonly string _accessRight;
public ActionUserAccessAttribute(string accessRight)
{
_accessRight = accessRight;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
throw new InvalidOperationException("ActionUserAccessAttribute can not be used for controllers or actions configured for anonymous access");
}
base.OnActionExecuting(filterContext);
var securityService = ContainerResolver.Container.GetInstance<ISecurityService>();
var hasResource = securityService.HasAccess(_accessRight);
if (!hasResource)
{
filterContext.Result =
new HttpStatusCodeResult(
403,
string.Format(
"User {0} is not authorized to access the resource:'{1}' ",
filterContext.HttpContext.User.Identity.Name,
_accessRight));
}
}
}
}
Porting the attribute/filter to aspnetcore seems quite straightforward, but according to this answer https://stackoverflow.com/a/31465227/1257728 by "asp.net security person" #blowdart we shouldn't.
If not porting the custom filter to aspnetcore, what would be the best fit to implement here?
Maybe we could use the Role based authentication https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?
We could create a middleware that populates the users access rights from the authorization service api and flatten the rights and add them as ClaimTypes.Role to the users' ClaimsIdentity ? Then we would use on the method above like:
[Authorize(Roles = "CanSaveContact")]
public ActionResult Save()
The misfit of this approach is that this is not really about roles, but more about the access rights.
I've also looked at the Policy based authorization:
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies
Which could look like this in the controller:
[Authorize(Policy = "CanSaveContact")]
public ActionResult Save()
But as I read the code in microsoft's policy based example above I would then have to add all available access rights that exists in the security service api as policies in the ConfigureService method of the Startup class to be able to use them. I think seems awkward (pseudo code):
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
IEnumerable<string> allAccessRights = _securtiyService.GetAllAccessRights();
services.AddAuthorization(options =>
{
foreach(var accessRight in allAccessRights)
{
options.AddPolicy(accessRight, policy => policy.Requirements.Add(new AccessRightRequirement(accessRight));
}
});
services.AddSingleton<IAuthorizationHandler, AccessRightHandler>();
}
The AccessRightHandler would then be resposible to validate the access right for the user. Writing an AccessRightHandler is ok, but it seems unnecessary to have to add all the rights as policies.
What would be the best approach to implement this kind of authorization in our aspnetcore application?
Great question, and I think a number of people would have the same problem upgrading to ASP.NET Core.
Barry Dorrans (#blowdart) is absolutely correct, you shouldn't write your own custom authorize attributes - Authorization in ASP.NET Core has been greatly improved, and you can definitely mould it to your needs.
It would of course greatly depend on your current application, and what roles do you have, so I'll make some assumptions based on the snippets you provided above.
Before I start, I REALLY recommend you read through the new Authorization docs for ASP.NET Core, as well as Barry Dorran's Authorization workshop on GitHub. I highly recommend you go through the latter, and he has a .NET Core 2.0 branch there as well.
Depending how you want to implement it, you could either go with Claims based authorization, or go resource based.
Looking at your roles, it seems like Resource based auth could actually work great in your case!
For example:
Identify possible operations (the operation Name is to be picked up from your Resources):
public static class Operations
{
public static OperationAuthorizationRequirement Access = new OperationAuthorizationRequirement { Name = "Access" };
public static OperationAuthorizationRequirement Copy = new OperationAuthorizationRequirement { Name = "Copy" };
public static OperationAuthorizationRequirement Edit = new OperationAuthorizationRequirement { Name = "Edit" };
public static OperationAuthorizationRequirement Save = new OperationAuthorizationRequirement { Name = "Save" };
public static OperationAuthorizationRequirement Delete = new OperationAuthorizationRequirement { Name = "Delete" };
}
Create a base resource authorization handler:
public abstract class BaseResourceAuthorizationHandler<TResource> : AuthorizationHandler<OperationAuthorizationRequirement, TResource>
{
private readonly string _resourceType;
public BaseResourceAuthorizationHandler(string resourceType)
{
_resourceType = resourceType;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement, TResource resource)
{
if (context.User.HasClaim("Resources", $"Can{requirement.Name}{_resourceType}"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
Implement specific resource based handlers. The resources are binding objects in your application to entities in your Resources. This class will be the glue between your current resource roles, the Operations, and the authorization system in ASP.NET Core. These can also be extended to add extra logic for any specific resource types/operations
For example, for Appointments:
public class AppointmentAuthorizationHandler : BaseResourceAuthorizationHandler<Appointment>
{
public AppointmentAuthorizationHandler() : base("Appointment") { }
}
Which you then register:
services.AddSingleton<IAuthorizationHandler, AppointmentAuthorizationHandler>();
Then in your controllers:
public class AppointmentsController : Controller
{
IAppointmentsRepository _appointmentsRepository;
IAuthorizationService _authorizationService;
public AppointmentsController(IAppointmentsRepository appointmentsRepository,
IAuthorizationService authorizationService)
{
_appointmentsRepository = appointmentsRepository;
_authorizationService = authorizationService;
}
public IActionResult Edit(int id)
{
var appointment = _appointmentsRepository.Get(id);
if (appointment == null)
{
return new NotFoundResult();
}
if (!(await _authorizationService.AuthorizeAsync(User, appointment, Operations.Edit)))
{
return new ChallengeResult();
}
return View(appointment);
}
}
You can also do the same in views, to check whether the user is allowed to see the Edit button, for example:
#using Microsoft.AspNetCore.Authorization
#model IEnumerable<Appointment>
#inject IAuthorizationService AuthorizationService
<h1>Document Library</h1>
#foreach (var appointment in Model)
{
if (await AuthorizationService.AuthorizeAsync(User, appointment, Operations.Edit))
{
<p>#Html.ActionLink("Appointment #" + appointment.Id, "Edit", new { id = appointment.Id })</p>
}
}
P.S. Just to add a note - yes, you lose the ability to filter by attributes, but in the end it's better this way. First and foremost - you move away from String based roles, you request permissions based on an operation type and resource type. Secondly, you can handle permissions in a much better (and intelligent way), as well as combine multiple permission checks.
It looks more complex, but it's also MUCH more powerful :)
Going to play the devil's advocate here, and suggest an alternative to my other answer - this could be a simpler option based on #mortb's request, and could fit some people that are migrating from their current systems.
Based on your situation, the Policy based auth really wouldn't fit your usecase - it's a more powerful option, you're not really using any of it, other than checking for the existence of a Resource string from your API.
On the other hand, I wouldn't discard the Roles approach. The resource list you get from the external API isn't strictly resources, but at the same time it maps quite perfectly to your needs. At the end of the day, all you're trying to do is to check whether the user has one (or more) Resource access permissions for a specific request.
Like you mentioned on your post, you'd have to extend your authorization to populate the roles from your external API. Don't forget that your ClaimsIdentity has a RoleClaimType property, which marks the type of the claim used to store the roles. It'll usually be set to ClaimTypes.Role, but not always.
You could even go ahead, and create custom auth attributes, not unlike this:
public class AuthorizeAccessAttribute : AuthorizeAttribute
{
public AuthorizeAccessAttribute(string entity)
{
Roles = "CanAccess" + entity;
}
}
public class AuthorizeEditAttribute : AuthorizeAttribute
{
public AuthorizeEditAttribute(string entity)
{
Roles = "CanEdit" + entity;
}
}
So you could use it as follows:
[AuthorizeEdit("Appointment")]
public IActionResult Edit(int id)
{
return View();
}
When i call my admin controller- Index Action method will get all the user details
when i want select particular user again i dont want to hit the DB.
both action method same controller and i'm using model popup for display details.
My Question
I dont want to use entity framework.
- when admin form load i will get all the user details this is Index Action Method
-based on user id i need to display particular user so again i dont want hit to the DB already i'm having all the user details. that details how to get another action method?
i can remember asp.net i used session to share the data globally. like that asp.net mvc is possible? please help me.
Thanks
It looks you're looking for a cache mechanism. For simple scenarios, I use a simple static variable, but I keep it in a separated class. Let's suppose you have a User class like this:
public class User
{
public string Id { get; set; }
public string Name { get; set; }
}
You could create a class like this:
public static class UserCacheService
{
private static IEnumerable<User> _users;
private static readonly object lockObj = new object();
public static IEnumerable<User> GetUsers()
{
lock (lockObj)
{
if (_users == null)
{
using (var db = new MyNiceDbContext())
{
_users = db.Users.ToList();
}
}
return _users;
}
}
public static void InvalidateCache()
{
lock (lockObj)
{
_users = null;
}
}
}
Then you can get your shared users in any action, of any controller like this:
public class AdminController : Controller
{
public ActionResult Index()
{
// the first time, it'll need to get users from DB (e.g with Entity Framework)
var users = UserCacheService.GetUsers();
return View();
}
}
The first time, the _users in your UserCacheService will be null, and as expected, it'll need to load users from database. However, the next time it won't, no matter if you are using another controller:
public class AnotherController : Controller
{
public ActionResult Index(string userId)
{
// now, it won't load from DB anymore, because _users is already populated...
var users = UserCacheService.GetUsers();
var currentUser = users.Where(u => u.Id == userId).FirstOrDefault();
if (currentUser != null)
{
// do something with the user...
}
return View();
}
}
There are times when unfortunately your _users will become null again, for example when you restart your ApplicationPool in IIS, but UserCacheService is already prepared for fetching database once if that's the case.
Be careful about three things:
Whenever you keep data in memory (like _users), you are consuming
your server's memory, which might be limited. Don't start trying to
keep everything in memory, only data you know you'll need everytime.
Whenever you update something in your users, like a name, an address or something else, since the _users will not get from database everytime, you need to call the UserCacheService.InvalidateCache() method, in order to force the next call to load again from database, thus making sure you have _users up to date.
This only works for simplistic scenarios. If you have your application distributed in two or more servers, this won't work, as each server has it's own memory and they can't share it out of the box. That's when you would look forward for something like Redis. Though, I don't think it's your case here.
So I've created a custom authorize attribute I use in a few places that is derived from an abstract base class which is derived from AuthorizeAttribute:
CustomAuthorizeAttributeBase.cs
public abstract class CustomAuthorizeAttributeBase : AuthorizeAttribute
{
public abstract string GetUsers();
public abstract string GetRoles();
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.IsChildAction)
{
return;
}
filterContext.Result =
new RedirectToRouteResult(new RouteValueDictionary
{
{"controller", "NotAuthorized"},
{"action", "Index"},
});
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (GetUsers().IndexOf(httpContext.User.Identity.Name, StringComparison.CurrentCultureIgnoreCase) >= 0 ||
GetRoles().Split(',').Any(s => httpContext.User.IsInRole(s)))
{
return true;
}
return false;
}
}
AreaLevelReadonly.cs
public class AreaLevelReadOnly : CustomAuthorizeAttributeBase
{
public override string GetUsers()
{
return ConfigurationManager.AppSettings["AreaReadonlyUsers"];
}
public override string GetRoles()
{
return ConfigurationManager.AppSettings["AreaReadonlyRoles"];
}
}
I also have some fairly simple code that gets me the currently logged in user:
UserIdentity.cs
public class UserIdentity : IUserIdentity
{
public string GetUserName()
{
return HttpContext.Current.User.Identity.Name.Split('\\')[1];
}
}
However, when I add my AreaLevelReadonly attribute to my controllers, getUserName fails and returns an exception that Name is null. I agonized over it for about an hour before putting authorize attribute on there as well, at which point it magically started working again. So, what is so different on the implementation level that my attribute deriving from authorizeattribute doesn't cause the Name to be populated.
Note: Windows authentication is on for the area, and the code works, but I don't understand why the Readonly attribute isn't enough to trigger authorization and population of the HttpContext.Current.User.Identity.Name.
Edit: Working:
[AreaLevelReadonly]
[Authorize]
public class DeleteAreaDataController : Controller {
//etc
var username = _userIdentity.GetUserName(HttpContext);
//etc
}
Exception on name:
[AreaLevelReadonly]
public class DeleteAreaDataController : Controller {
//etc
var username = _userIdentity.GetUserName(HttpContext);
//etc
}
More likely than not, you're accessing User.Identity.Name before it's populated. By including the standard Authorize attribute, as well, your code is then only running after the user has been authorized already and User.Identity.Name has been populated.
EDIT
Sorry, I misunderstood where the code attempting to call User.Identity.Name was running. Based on the belief that it was happening in your custom attribute, I was suggesting that you're trying to access it too early. However, I now see that you're calling it in your controller (although an explanation of what happens in GetUserAccount(HttpContext) would have helped.)
Anyways, your custom attribute obviously adds extra conditions on whether a user is authorized or not. When you return false, there is no user. It's not a situation where the user is "logged in" but not allowed to see the page. It's either there or it isn't. So the user is failing authorization based on your custom attribute (User.Identity.Name is null) but is authorized when you include Authorize (User.Identity.Name has a value).
Long and short, your GetUserName or GetUserAccount or whatever code needs to account for when the user has failed authorization. Or, if the user shouldn't be failing authorization, you'll need to look into why your custom attribute isn't working. Though, either way, you should still account for User.Identity.Name being null.
Your custom attribute is probably reading User.Identity.Name before you check that the user is authenticated.
In other words, in IsAuthorized(), before you read User.Identity.Name, you should be doing something like this:
if (!user.Identity.IsAuthenticated)
{
// Your custom code...
return false;
}
The reason you need this is because Windows Authentication (at least for NTLM) is a 2-step negotiation process between the client and server (see https://support.citrix.com/article/CTX221693). There will be 2 requests - the first with no name, and the second with a name. You can test this yourself - the source code for AuthorizeAttribute is provided here. Copy/paste that into your code and put a breakpoint in IsAuthorized - you will see that the breakpoint is hit twice. First time, the name is null, second time, it's set to your username.
So I think the solution is to either check user.Identity.IsAuthenticated at the start of your method, if you need to run custom code (as shown above), or alternatively if you only need to return false, simply replace the above code with base.IsAuthorized() which should do it for you.
Designing an ASP.Net MVC application authorization using claim based model. Lets say that we have an object called - Product. Typically, there are 4 different actions - Create, Edit, Delete and View. Authorization is done using ClaimsAuthorize attribute.
[Authorize]
public class ProductController : Controller
{
[ClaimsAuthorize("Product", "VIEW")]
public List<Product> GetProducts()
{
// ....
}
[ClaimsAuthorize("Product", "CREATE")]
public Product CreateNewProduct(Product product)
{
//....
}
}
But in my case, I have to support different types of EDIT permissions:
Some Users can Edit the product if the same user has created the Product originally
Some users can Edit the product if the Product belongs to a specific category and the user also has access to the same category
Some users can Edit all the products (this is the normal Product Edit operation)
How do you elegantly authorize all these Edit operations (preferably attribute driven as shown above) and at the same time I want to keep the authorization code separate from the normal MVC controller code and business logic.
[Above code sample is not syntactically correct, I just made it up for the purpose of explaining this question]
Let me know your thoughts.
For first part of your question, Claim based authorization, I have already answered it in this similar question. And I am not going to repeat here.
But for your another rules like products editable only by owner. You could write separate AuthorizeAttribute for each rule and apply them on your Actions consider this as an simple example:
using Microsoft.AspNet.Identity;
public class OwnerAuthorizeAttribute : AuthorizeAttribute
{
private string _keyName;
public bool IsPost { get; set; }
public OwnerAuthorizeAttribute(string keyName)
{
_keyName = keyName;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// imagine you have a service which could check owner of
// product based on userID and ProductID
return httpContext.User.Identity.IsAuthenticated
&& this.ContainsKey
&& _productService.IsOwner(httpContext.User.Identity.GetUserId(),
int.Parse(this.KeyValue.ToString()));
}
private bool ContainsKey
{
get
{
return IsPost
? HttpContext.Current.Request.Form.AllKeys.Contains(_keyName)
// for simplicity I just check route data
// in real world you might need to check query string too
: ((MvcHandler)HttpContext.Current.Handler).RequestContext
.RouteData.Values.ContainsKey(_keyName);
}
}
private object KeyValue
{
get
{
return IsPost
? HttpContext.Current.Request.Form[_keyName]
// for simplicity I just check route data
// in real world you might need to check query string too
: ((MvcHandler)HttpContext.Current.Handler)
.RequestContext.RouteData.Values[_keyName];
}
}
}
You could repeat same pattern to your other rules too.
And you could simply apply your custom attributes to your actions:
[OwnerAuthorize("id")]
public ActionResult Edit(int id)
{
// your code
}
[HttpPost]
// double checking in post back too
[OwnerAuthorize("id", IsPost = true)]
public ActionResult Edit(Product product)
{
// your code
}
It is obvious you could apply more then one AuthorizeAttribute to your actions. In this case all of them must return true.
[ClaimsAuthorize("Product", "EDIT")]
[OwnerAuthorize("id")]
[YetOtherAuthorize]
public ActionResult MyFancyAction(int id)
{
}
Is it possible to bypass the authorization role check on a controller, but enforce the role check on an action? I've spent a bit of time researching this and everything I find shows how to implement an AllowAnonymousAttribute. I'm currently using the AllowAnonymousAttribute and it works great for completely bypassing authorization for an action. That isn't what I want. I have a controller that requires certain roles. When a particular action is requested I want to skip the roles at the controller level and just verify user has the roles designated on the action.
Here's some code:
[Authorize(Roles="Administrator")]
public class MembersController : ViewApiController<MemberView>
{
// a list of actions....
[Authorize(Roles="ApiUser")]
[HttpPost]
public void AutoPayPost([FromBody] List<AutoPayModel> autoPayList)
{
//....
}
}
The problem is I want users with just the 'ApiUser' role to have access to the 'AutoPayPost' action. I realize I can remove the class level authorize attribute, then add it to every action method on my controller, minus the 'AutoPayPost' action. I would like to avoid this because several of my controllers inherit from a base class that provides a long list of actions that require the 'Administrative' role. Because of that I would have to override every base action, add the Authorize attribute to the overridden method, then delegate the call back to the base class. This WILL work but if I later decide to add functionality to the base class I'll have to remember to go back to the MembersController and override the new methods, add the attribute etc...
It would be great if the end result looked like this:
[Authorize(Roles="Administrator")]
public class MembersController : ViewApiController<MemberView>
{
// a list of actions....
[Authorize(Roles="ApiUser", IgnoreControllerRoles=true)]
[HttpPost]
public void AutoPayPost([FromBody] List<AutoPayModel> autoPayList)
{
//....
}
}
Do something like this, where you will check if the roles/users are in the roles and then deny any of them.
public class ByPassAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
string[] roles = this.Roles.Split(',');
string[] users = this.Users.Split(',');
foreach (var r in roles)
{
if (httpContext.User.IsInRole(r.Trim()))
return false;
}
foreach (var u in users)
{
if (httpContext.User.Identity.Name.Equals(u))
return false;
}
return base.AuthorizeCore(httpContext);
}
}
And then decore your controller/action like this:
[ByPassAuthorize(Roles = "Admin,test,testint", Users = "Tester")]
public ActionResult Edit(int id = 0)
{
FooModel foomodel = db.FooModels.Find(id);
if (foomodel == null)
{
return HttpNotFound();
}
return View(foomodel);
}
Hope its help you!
If I understand you correctly, you could implement a custom ByPassControllerChecksAttribute (it is for decorating methods that you want to allow "passthrough" access to), then in your LogonAuthorizeAttribute retrieve the action method being called by this request and check if its custom attribute collection has an instance of ByPassControllerChecksAttribute. If it does, run the code that checks if the user is allowed access to the method, otherwise run the code that checks if the user is allowed access to the controller. Of course if you have just one method and the name is known not to change, you can bypass the extra attribute and just check for the name, but of course the first method is much better.
EDIT
If your LogonAuthorizeAttribute inherits from AuthorizeAttribute then you can override the AuthorizeCore method which returns a boolean (true meaning the user is authorized, false otherwise). In this method you can have something along the following pseudocode:
if(CheckIfMethodHasByPassAttribute()){
return CheckIfUserIsAllowedToRunThisMethod();
}
return CheckIfUserIsAllowedToRunThisController();
The method CheckIfUserIsAllowedToRunThisMethod would have whatever checks you need to do to determine if a user is allowed to run this method, while the CheckIfUserIsAllowedToRunThisController would have the code to check if a user is allowed access to the controller in general (which I assume is already in you LogonAuthorizeAttribute)