authorize logged in user against url - asp.net-core

My goal is to authorize users only if the current logged on user's customerId matches the customerId on the url/controller.
I'm using ASP.NET Core 2.0 with entity framework. I have extended ApplicationUser with a CustomerId int that has a FK to a CustomerIdentity table.
I am able to retrieve the customerId from the logged in user, using this method here: http://rion.io/2016/01/04/accessing-identity-info-using-dependency-injection-in-net-5/
I have made a routing:
routes.MapRoute(
name: "customers",
template: "{customerId?}/{controller=Home}/{action=Index}/{id?}");
And then in my controllers I have a customerId parameter.
Global admins have the rights to use any customerId they like, but for everyone else they can only use their customerId belonging to their logged in user.
I was thinking of using this approach to check if customerId from url, e.g. /23/Computers/Approve matches currentCustomerId
But I'm not sure how to adapt it to a policy and claim that asp.net core uses.
What I have sofar:
public class CustomerRequirement : IAuthorizationRequirement
{
public bool IsMatchingLoggedInUser { get; private set; }
public CustomerRequirement(bool isMatchingLoggedInUser)
{
IsMatchingLoggedInUser = IsMatchingLoggedInUser;
}
}
Not sure how to make this one:
public class CustomerRequirementHandler : AuthorizationHandler<CustomerRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomerRequirement requirement)
{
//GetDate.CurrentCustomerId is a static int property set after user has logged in
if (GetData.CurrentCustomerId == /*Get httpContext customerId ??? "")*/ 42)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
Not sure if I'm totally off here or it can be done differently? I am open for suggestions.
Update (what I ended up with)
public class ValidateCustomerAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (GetData.CurrentCustomerId != (int)context.ActionArguments["customerId"])
{
context.Result = new UnauthorizedResult();
}
}
}
Then on my controllers I decorated it with this attribute. I still need to exclude admins in this, but that will come later. (and some more errorhandling :))

I'm not familiar with how ASP.NET Core does this (is AuthorizationHandler similar to an action filter?), but this seems like something you can handle using roles.
I have a custom Auth action filter where I put custom validation logic like this. In the controller, you could check the roles of the logged in user. If they don't have role "Admin" and their customer ID does not match the customer ID in the URL, return a 403. Otherwise, continue as normal.
If getting the value of the customer ID is a problem for you and you don't have access to the action parameters, then you can use the current request's Uri, split the path apart and pull it out that way. But that's a fragile implementation.

Related

.NET Core Identity API with permission based auth

I'm new at Identity API but in my web application: Institution users creates other users for own institution and and they want to decide who see this page or not.My controller methods like this ;
[Authorize]
public IActionResult Privacy()
{
return View();
}
But also user's have permissions to do any actions like this enum and enum is bigger than 50;
public enum PermissionTypes
{
UserCreate = 1,
UserEdit = 2,
UserDelete = 3,
....
}
And i do some research and found policy based authorization but when you create a new policy you must declare at Startup.cs and its not good for me because when you do that you always publish new codes in production.What i need is something like that ;
[CustomAuth(PermissionTypes.UserCreate)]
public IActionResult Privacy()
{
return View();
}
Is there any solution for this situation ?
There is many ways to do this. A lot of people recommend claims and policy based security... I personally found this approach a little "stiff".
So instead I do this a little different:
First create a class like this:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.AspNetCore.Identity;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
namespace Bamboo.Web.CoreWebsite.Membership
{
public class PermissionHandler : AuthorizationHandler<RolesAuthorizationRequirement>
{
private readonly IUserStore<CustomUser> _userStore;
public PermissionHandler(IUserStore<CustomeUser> userStore)
{
_userStore = userStore;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement)
{
if(context == null || context.User == null)
return;
var userId = context.User.FindFirst(c => string.CompareOrdinal(c.Type, ClaimTypes.NameIdentifier) == 0);//according to msdn this method returns null if not found
if(userId == null)
return;
// for simplicity, I use only one role at a time in the attribute
//but you can use multiple values
var permissions = requirement.AllowedRoles.ToList();
var hasPermissions = //here is your logic to check the database for the actual permissions for this user.
// hasPermissions is just a boolean which is the result of your logic....
if(hasPermissions)
context.Succeed(requirement);//the user met your custom criteria
else
context.Fail();//the user lacks permissions.
}
}
}
Now inject the PermissionHandler in your startup.cs file like this:
public void ConfigureServices(IServiceCollection services)
{
// Custom Identity Services
........
// custom role checks, to check the roles in DB
services.AddScoped<IAuthorizationHandler, PermissionHandler>();
//the rest of your injection logic omitted for brevity.......
}
Now use it in your actions like this:
[Authorize(Roles = PermissionTypes.UserCreate)]
public IActionResult Privacy()
{
return View();
}
Notice I did not create a custom attribute... Like I said there is many ways to do this.
I prefer this way because is less code and there is no hard-coded policies or claims or any other complexities and you can make it 100% data driven.
This is a complex subject so there might be extra tweaks necessary for it work.
Also I use ASP.NET Core 2.2 which might be different than 3.0.
But it should give you a way to do permission based Authorization.
You need to use Roles within your action.
ASP .NET Core Identity Roles

A way to set the value of one property after model is bound asp.net core

I am creating an asp.net core 2.2 api. I have several model classes, most of which contains a string property CreatorId:
public class ModelOne
{
public string CreatorId { get; set; }
//other boring properties
}
My controllers' actions accept these models with [FromBody] attribute
[HttpPost]
public IActionResult Create([FromBody] ModelOne model) { }
I don't want my web client to set the CreatorId property. This CreatorId field appears in tens of classes and I don't want to set it in the controlers actions manually like this:
model.CreatorId = User.Claims.First(claim => claim.Type == "id").Value;
I can't pollute the model classes with any custom model-binding related attributes. I don't want to pollute the controllers or actions with any model binding attributes.
Now, the question is: is there a way to add some custom logic after model is bound to check (maybe with the use of reflection) if model class contains CreatorId field and then to update this value. If it is not possible from ControllerBase User property, than maybe by looking at the jwt token. The process should be transparent for the developer, no attributes - maybe some custom model binder registered at the application level or some middleware working transparently.
Ok, I've invented my own - action filter based solution. It seems to work good. I've created a custom action filter:
public class CreatorIdActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ActionArguments.ContainsKey("request")) return;
var request = context.ActionArguments["request"];
var creatorIdProperty = request?.GetType().GetProperties().FirstOrDefault(x => x.Name == "CreatorId");
if (creatorIdProperty == null) return;
var user = ((ControllerBase)context.Controller).User;
creatorIdProperty.SetValue(request, user.Claims.First(claim => claim.Type == "id").Value);
}
public void OnActionExecuted(ActionExecutedContext context) { }
}
which was just registered in ConfigureServices
services.AddMvc(options => { options.Filters.Add(typeof(CreatorIdActionFilter)); })
no other boilerplate code is needed. Parameter name is always "request", that's why I take this from ActionArguments, but of course this can be implemented in more universal way if needed.
The solution works. I have only one question to some experts - can we call this solution elegant and efficient? Maybe I used some bad practices? I will be grateful for any comments on this.

How can I get the user from my AuthorizationAttribute from within an ASPNETCORE 2.2 application?

I have created a custom attribute that I would like to decorate my api controller from within my ASPNETCORE angular application. I am able to set up my authentication as required and log into the application from the login. Then I decorate my api method with my custom attribute.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false)]
public class ManageAuditAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public List<Claim> Claims { get; set; }
public ManageAuditAttribute(String feature)
{
Feature = feature;
}
public bool IsAuthorized()
{
// TODO check there is a claim for the given feature
}
private String Feature { get; }
public void OnAuthorization(AuthorizationFilterContext context)
{
var accessor = (IHttpContextAccessor)context.HttpContext.RequestServices.GetService(typeof(IHttpContextAccessor));
var name = context.HttpContext.User.FindFirst(ClaimTypes.Email); //NULL
var user = context.HttpContext.User; // NULL
var userName = Thread.CurrentPrincipal.Identity.Name; // NULL
}
}
Before saying that claims are not used this way I would add that I need to fit this into a legacy system that has a list of allowed features. I am adding those features to the user as claims and checking the claim exists for each user. The value for the actual claim is the name of the application that the user needs the claim for.
I could easily add these as a custom list to my custom identity user which might be more fitting however, I still need to access my user.
I can get the user but the name is always null and my claims list is empty as well. It is as if I am not logged in at all. Even after logging in.
For retrieving name, you should pass ClaimTypes.Name instead of ClaimTypes.Email like
var user = context.HttpContext.User.FindFirst(ClaimTypes.Name);
Or, you could retrieve by HttpContext.User.Identity.Name like
var userName = context.HttpContext.User.Identity.Name;
I have solved you problem by adding:
app.UseAuthentication();
in Startup.cs.

OverrideAuthorizationAttribute in ASP.NET 5

I would like to implement the following in MVC6:
[Authorize(Roles = "Shopper")]
public class HomeController
{
[Authorize(Roles = "Editor"), OverrideAuthorization]
public IActionResult EditPage() {}
}
But OverrideAuthorizationAttribute no longer exists. So how do you set it so that a user only needs to be in the Editor role and not Editor and Shopper role to access EditPage in MVC6?
I found this blog post from Filip W that explains how write your own solution using the filter providers.
However the framework has changed a lot and his solution has to be updated to take into account the changes in the framework up to beta8.
First you will create a new attribute where you can specify the type of the filter that you want to override. (In your case this would be the AuthorizeFilter)
public class OverrideFilter : ActionFilterAttribute
{
public Type Type { get; set; }
}
If you want. you could create more specific filters like:
public class OverrideAuthorization : OverrideFilter
{
public OverrideAuthorization()
{
this.Type = typeof(AuthorizeFilter);
}
}
Then you need to create a new IFilterProvider.
This filter provider will be executed after the default providers in
the framework have run.
You can inspect the
FilterProviderContext.Results and search for your OverrideFilter
If found, you can then inspect the rest of the filters, and delete
any filter that is of the filtered type and a lower scope
For example create a new OverrideFriendlyFilterProvider following this idea:
public class OverrideFriendlyFilterProvider : IFilterProvider
{
//all framework providers have negative orders, so ours will come later
public int Order => 1;
public void OnProvidersExecuting(FilterProviderContext context)
{
if (context.ActionContext.ActionDescriptor.FilterDescriptors != null)
{
//Does the action have any OverrideFilter?
var overrideFilters = context.Results.Where(filterItem => filterItem.Filter is OverrideFilter).ToArray();
foreach (var overrideFilter in overrideFilters)
{
context.Results.RemoveAll(filterItem =>
//Remove any filter for the type indicated in the OverrideFilter attribute
filterItem.Descriptor.Filter.GetType() == ((OverrideFilter)overrideFilter.Filter).Type &&
//Remove filters with lower scope (ie controller) than the override filter (i.e. action method)
filterItem.Descriptor.Scope < overrideFilter.Descriptor.Scope);
}
}
}
public void OnProvidersExecuted(FilterProviderContext context)
{
}
}
You need to register it on the ConfigureServices of your startup class:
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IFilterProvider, OverrideFriendlyFilterProvider>());
With all this pieces you will be able to override the authorization filter (or any other filter).
For example in the default HomeController of a new mvc application, any logged in user will be able to access the Home action, but only the ones with the admin role will be able to access the About action:
[Authorize]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[Authorize(Roles = "admin"), OverrideAuthorization]
public IActionResult About()
{
return View();
}
I think it would be better to use the new policy based authorization approach instead of using roles directly.
There is not a lot of documentation yet about policy based authorization but this article is a good start

ASP.Net/MVC Authorize Vs Authenticate

So I set this above my Controller:
[Authorize(Roles="Administrator")]
The problem is whether they are not logged in, or don't have the right role, it redirects them to the login page. Is there a way to have it handle authorization and authenticate differently?
I might not understand you clearly, but authentication and authorization are always coming together.. One says which mechanism use to authenticate user (forms, windows etc.), and second which roles or users are allowed to see the content...
As far as authentication method is set in your web config it is fixed, and only think you can use to protect your controller methods is to put those attributes.
Also if you want to use it diffrently, f.e. redirect to diffrent page you can use following code:
public class RedirectAuthorizeAttribute : AuthorizeAttribute
{
public string RedirectUrl { get; set; }
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectResult(RedirectUrl);
}
}
and then put it onto your controller method like that:
[RedirectAuthorize(Roles = "MyRole", RedirectUrl = "SomeUrl")]
public ActionResult SomeAction()
{
...
}