I have a working authorization based on roles like this.
[Authorize(Roles = "Super, Common")]
[Route("api/[controller]")]
public class MemberController : Controller
{
...
[HttpGet("Member/{id}")]
public IActionResult GetMember(Guid id)
{
Member output;
...
return Ok(output);
}
}
The problem now is that any member can see any other. I'd like the method to be authorized so that:
If the given member has the role of "common" (the ID is in the claims), they can fetch info about themselves.
If the given member has the role of "super", they can fetch info about themselves but also about any other member in their organization.
If the given member has the role of "admin", they can fetch info about themselves and any member in their organization but also about any other member in the database as a whole.
My way to resolve it is to build in logic in the controller fetching and checking. However, I know that there are security policies in the framework, which can be configured in the Start class.
I wonder if said task is possible to resolve using policy configuration to begin with or if I'm doomed to hack the logic myself in the method of the controller. I can't determine it myself as I lack the competence. Is that a feasible strategy?
Related
I've got a web api that accepts authentication through either an api key, a user access token, or a client access token. I've already written a DelegatingHandler for each case, that creates a new ClaimsPrincipal based on the given authentication details, and have confirmed that the principal is accessible within a controller action.
What I want to do now is inject either a company, a user, or a publisher into the route values, so I can create overloads on the controller for each case. What class/interface do I need to extend in order to plug into the pipeline with the following conditions:
Has access to the current principal
Has access to the route data
The action has not yet been selected
Edit
I'm not looking to sidestep routing here - I still want MVC to choose a route for me, based on the route values. I just want to add one more parameter to the route values before it chooses, and the parameter will have a different name and type depending on whether a user access token is used, or an api key is used.
I believe the subject of authentication should result in two distinct methods, because an api key can access all resources for a company, while a user access token can only access the resources they have been given permission to view.
I do not see a reason why you would want to go with a controller here. You would be sidestepping routing, a very opinionated piece of MVC. I would create middleware that runs before MVC (which is, itself, just middleware) instead.
If you're looking to affect RouteData inline, I would look into using a global IResourceFilter or IAsyncResourceFilter. Then, you can update the RouteData property on the ResourceExecutingContext based upon the conditions you specified in your question.
Any additional dependencies you need to determine how to populate the RouteData property can be injected into the resource filter's constructor as specified in the section on dependency injection.
public class SetRouteValueResourceFilter : IAsyncResourceFilter {
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) {
var company = context.HttpContext.Identity.FindFirst("Company")?.Value;
if (company != null) {
context.RouteData.Values.Add("company", company);
}
await next();
}
}
This answer is just an idea. Even though this is handled before the action logic, I'm not sure it will affect which route is selected. According to this diagram, routes are selected before filters are ran.
I managed to get this going by making a custom ValueProviderFactory, which reads values from the current principal's claims and makes them available for parameter binding:
public class ClaimsPrincipalValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(HttpActionContext actionContext)
{
if (actionContext.RequestContext.Principal != null && actionContext.RequestContext.Principal is ClaimsPrincipal principal)
{
var pairs = principal.Claims.Select(claim => new KeyValuePair<string, string>(claim.Type, claim.Value));
return new NameValuePairsValueProvider(pairs, CultureInfo.CurrentCulture);
}
return null;
}
}
In order to use it, you can annotate the input parameters with the ValueProvider attribute:
public class FooController : ApiController
{
[HttpGet]
public void Bar([ValueProvider(typeof(ClaimsPrincipalValueProviderFactory))]ApiKey apiKey)
{
// ...
}
}
That's pretty ugly and unreadable, what I really wanted was something like the FromUri attribute, but for claims. ValueProviderAttribute and FromUriAttribute both inherit from ModelBinderAttribute, so I created a class which does the same:
/// <summary>
/// The action parameter comes from the user's claims, if the user is authorized.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public class FromClaimsAttribute : ModelBinderAttribute
{
public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
{
return parameter.BindWithModelBinding(new ClaimsPrincipalValueProviderFactory());
}
}
Now the example method on the FooController looks a lot more readable:
[HttpGet]
public void Bar([FromClaims]ApiKey apiKey)
{
// ...
}
Update
Looks like this is still having problems with route selection and overloading, especially when some of the parameters are nullable and the value is null. I'm going to have to keep looking into this.
Update #2
I managed to simplify the value provider stuff a lot, after finding there is a built-in NameValuePairValueProvider
Is the following API of a claims authorization service ok, from the point of view of usability?
/* before UPDATE it was like this:
var canEdit = Authz.ForNewRequest()
.WithActionName("edit")
.WithResourceName("project")
.WithResourceClaim("DepartmentId","21")
.CheckAccess(); */
//UPDATE:
var deptId = SomehowGetDepartmentIdAtRuntime(targetProject);
var canEdit = Authz.ForNewRequest()
.WithActionName("edit")
.WithResourceName("project")
.WithResourceClaim("DepartmentId",deptId)
.CheckAccess();
if (canEdit)
{
//edit project
}
And configuration like this:
var authorizationModel = Authorization.ConfigNamespace.AuthzConfig
.LoadAuthorizationModelFromXml("authz.xml");
Authorization.ConfigNamespace.AuthzConfig
.SetApplicationAuthorization(authorizationModel);
Or custom configuration like this:
var authzCustomConfig = Authorization.ConfigNamespace.AuthzConfig
.NewCustomConfiguration()
.WithCustomClaimBasedFactFunctions(claimBasedFunctions)
.WithCustomClaimProviders(claimProviders)
.WithCustomCompositeFactFunctions(compositeFactFunctions)
.WithCustomObligations(obligations);
var authorizationModel = Authorization.ConfigNamespace.AuthzConfig
.LoadAuthorizationModelFromXml("authz.xml", authzCustomConfig);
Authorization.ConfigNamespace.AuthzConfig
.SetApplicationAuthorization(authorizationModel);
Basically, the question is about the top part of the iceberg, i.e. how to use the service, but not how to implement or design the inner part. But just in case, here are a couple of general words about this service:
This service gives an answer of true/false for the given authorization request.
Authorization request has information about:
Action (action name, for example)
Resource (resource name, resource properties)
Subject (user name, user id, user roles, user properties)
Due to the fact that i use Microsoft.IdentityModel:
The properties of an action, resource, or a subject are presented as Claims. Approximately, a claim is an object, which has a value, value name, and value type. For example, a claim for a user could have the following info: ("UserName", "Andrey", "string").
The authorization request is the AuthorizationContext class from the Microsoft.IdentityModel.Claims namespace.
Two more things to consider:
I'd like this service to be a general solution, which could fit not only to one certain project.
Logic could be involved in order to understand if a condition for the permission decision is met or not.
That's why custom logic might be injected. Those claimBasedFunctions, claimProviders, compositeFactFunctions, and obligations are the custom logic. But they don't really matter for the question, just some custom confuguration elements, implementations of the interfaces, which are defined in the authorization assembly. The question is not about what they should be, or how they should work. You can think of them as of any interface implementatons that have to be injected.
Thanks!
P.S. this question is off-topic for the Code Review site.
If I interpret your description correctly, the following code means: "If you want to edit a project, you need a claim with name DepartmentId that has a value of 21.
var canEdit = Authz.ForNewRequest()
.WithActionName("edit")
.WithResourceName("project")
.WithResourceClaim("DepartmentId","21")
.CheckAccess();
if (canEdit)
{
//edit project
}
This statement would be at the start of your Edit action in your Project controller in case you were building an MVC application.
If my interpretation is correct, I would advise you to remove the WithActionName and WithResourceName methods. Those things can be retrieved from the context in which this code is executing. Your fluent API is too easy to copy from one method to another and forget to update those strings. I would look at a custom authorize attribute that you attach to an action in which you checks the claims.
UPDATE:
I was thinking something like this:
public class ProjectController : ApiController
{
[HttpPost]
[MyAuthorize("DepartmentId","21")
public void Edit(string applicationName)
{
// business logic
}
}
Inside the MyAuthorize attribute implementation you can retrieve the controller- and action names. If the developer using the attribute doesn't have to specify this, he/she can't get it wrong.
Using webapi with formsauthentication (i know may sound weird, but it exactly what I need).
I have a route like :
[Route("{application}/orders}"]
public IHttpActionResult Get(string application) {
var orders = OrderService.GetOrders(application); // return list of orders of applicaiton.
return Ok(orders);
}
Example scenario :
User John has permission to see orders for application "CoolApp"
User Leia has permission to see orders for application "Robin"
user Tristan doesn't have permission to see orders.
On the DB is like a Role - Resource relationship.
UserRole:
User - Role - Application
=====================================
John - OrdersManager - CoolApp
Leia - OrdersManager - Robin
Tristan - OtherRole - Robin
I want that route to check if the user has permission to get orders of that specific application.
I read this: Resource based authorization in .net and looks promising.
First thing is I'm not sure if I should extend using AuthorizeAttribute, AuthorizationFilterAttribute or IAuthorizationFilter as briefly explained here http://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api unfortunately, is not of much information.
Second I read that binding happens AFTER the authorization so I don't know how to get the values from the RouteData since I have the {application} defined on the route data I guess I should be able to access it. There are few examples for MVC, but was not able to get one for web api and with object like request and context all over the actionContext object I'm not sure where to get it and how in a proper way.
And last but not least, what method of the chosen extension point(s) should I override. If it have both async and sync, should I override both ?
BONUS: Since the authorize will have a dependency on my repository, would be great to know how to inject that into the filter if that's even possible. (I'm not using any IoC container).
Overriding AuthorizationFilterAttribute would probably be the better route. What you'd want to do is pass the attributes you want to check for:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
string role;
string application
public MyAuthorizeAttribute (string role, string application){
this. role = roles;
this.application = application;
}
protected override bool IsAuthorized(HttpActionContext actionContext) {
var routeData = actionContext.ControllerContext.RouteData;
//do your checks
}
}
you can get access to routedata from the HttpActionContext in the IsAuthorized method. Now you can attach the attribute to your ApiController or Action Method.
I'd suggest using an IoC container is you want to do DI with an Web API ActionFilter
Right now I have the following website structure:
Front end: website\user1, website\user2
Back end: website\account\user1, website\account\user2
Where website\account controller has generic [Authorize] attribute applied. Which is not enough since any authorized user can access other user's backed functionality simply by going to website\account\ url (if he knows his name).
What is the best way to resolve this issue?
I have two approaches so far:
Create custom Authorize attribute, inspect controller context, extract user information from there and compare it against current authorized user in ASP.NET:
var currerntUserId = (long)System.Web.Security.Membership.GetUser().ProviderUserKey;
return ExtractCurrentUserId(filterContext) == currerntUserId;
Remove part from back-end URLs and have all users access \account. Current user information will be provided by ASP.NET framework.
How about this?
Route /website/my-account
[Authorize]
public class MyAccountController : Controller
{
public ActionResult Index()
{
var userData = System.Web.Security.Membership.GetUser();
// note you could also get this from db using this.User.Identity.Name
return View(userData);
}
}
It is much easier to control the authorization this way because we are not passing the userid to the action method via a route parameter. The only way someone can get to the backend for a particular user account is by being logged in as that user.
Reply to comments:
To answer your questions in comments about what is easier / harder / better / what my preference is, I am going to go ahead and make my final answer "It depends."
It depends on the sensitivity of the data, what things admins can do that users aren't allowed to (or vice versa), how many controller actions needed to be secured, how similar the views are for public / account / admin perspectives on the data, etc. Pretty much everything stated in your question and all the answers here are valid approaches. You can certainly do it with an ActionFilter and keep the user URL's, or you could do it directly in the action method (if there aren't a lot of them), change your URL schema, implement impersonation (or not), etc.
When you are retrieving a user's data from your datastore (most likely a database), you should only retrieve data for the username of the authenticated user. In your controller, this will give you the username of the currently authenticated user:
User.Identity.Name
So you could do something like:
return ExtractCurrentUserId(filterContext) == User.Identity.Name;
If you use Role based authentication with SimpleMembership you can do something like this and give users roles that should be able to access certain controller actions:
public class MyAccountController : Controller
{
[Authorize(Roles = "Admin")]
public ActionResult User1()
{
// do user1 work
}
[Authorize]
public ActionResult User2()
{
// do user2 work
}
}
I have 3 different types of users (with different roles) interacting on my web application, they all perform some task - some can be exactly the same e.g. create a quote others can be unique to that specific user e.g. sign off quote.
For more clarity 3 types of users: Client, Supplier, Customer.
Client or Customer can create a quote, however only the Customer can sign off a quote.
How do I ensure my application allows clients to access client speficic controllers and suppliers to access supplier specific controllers or areas. Via Custom Attributes? Do I store the type of user inside a cookie? Is this safe? or Session state? As soon as someone logs onto the system I send back a LoggedOnDTO object on which I store Username, UserID, and type of user....
NOTE: I went away from asp.net build in way of creating users, I have my own custom tables with my custom mechanism for logging into the system. I have a registered Model Bindiner that looks for the prefix and I send in a strongly typed object to each action...
Sample code:
[HttpGet]
public ActionResult AddComment(int quoteid, ClientUserDTO loggedonclientuser)
{
}
[HttpGet]
public ActionResult AddCommentSupplier(int quoteid, Supplier loggedonsuppluser)
{
}
EDIT: This method for some reason seems so much simpler... Is there something wrong with it? Any possible security issues? Threading?
My session controller is:
if (_authService.isValidUser(model))
{
var data = _authService.GetAuthenticationCookieDetails(model);
AuthenticateCookie.AddDetailsToCookie(data);
return Redirect(Url.Action("Index", "Activity"));
}
When I create my cookie... I can simple store "ClientUser", "Supplier" or whatever role they are inside the cookie.
Then I can create an Attribute and read in the cookie data to see if they are a valid user e.g.
public class ClientAuthorizationAttribute : AuthorizeAttribute
{
public bool AlwaysAllowLocalRequests = false;
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
{
if (AlwaysAllowLocalRequests && httpContext.Request.IsLocal)
{
bool authorized = false;
var result = UserDetails.GetTypeFromTicket(httpContext.User.Identity as FormsIdentity);
if (result.Equals("client", StringComparison.OrdinalIgnoreCase))
{
authorized = true;
}
//throw no access exception?
return authorized;
}
return base.AuthorizeCore(httpContext);
}
}
Register the attribute under my base controller and I have a simple working solution???
Write a custom MembershipProvider and a Custom RoleProvider then you can decorate your controler class or specific methods with the attribute
<Authorize(Roles:="ROLENAME")>
You can learn how to make that your asp mvc use the custom membershiprovider in this question It's really easy.
Edited:
The way you did it looks right, but I think you take the long way. Implementing your own MembershipProvider and your own Roleprovider will take you no more than 20 minutes... and you will have the benefits of being working with a well tested and documented system and still having the benefits of use your own database tables for the login. In a simple login system, you only have to write two functions in the roleprovider (GetRolesForUser and IsUserInRole) and only one function in the membershipprovider (ValidateUser) and you will get your system working.
If you want, I can put somewhere (maybe pastebin) a well commented versiĆ³n of a membershipProvider as well of a roleprovider that i'm using in a simple app (they're made in vb.net but i'm sure it will not be a problem)
You can also write generic code in the base controller instead of decorating each action with Authorize attribute. Please refer below url.
Good practice to do common authorization in a custom controller factory?
custom-controller-factory/5361846#5361846