Conditionally Ignore Authorize .NET Core 3.1 - c#

I am migrating a web API from .net Framework to .net Core. The old version was able to ignore the Authorize Attribute on the controller if the app was running on a private server. Here is the code for that. I know .net core 3.1 does not offer Custom AuthorizeAttributes. That is not my question.
// A custom AuthroizeAttribute
public class ConditionalAuthorizeAttribute : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext httpContext)
{
if (environment_private())
return true;
else
return base.IsAuthorized(httpContext);
}
private bool environment_private()
{
// code that will tell you if you are in your dev environment or not
return Properties.Settings.Default.PrivateServer;
}
}
// How it was called from the controller
[ConditionalAuthorize(Roles = "MyRole")]
[Route(...)]
// More code for controller
I just need a simple way to authorized all requests when running the project on our private server (which is determined by a variable in appSettings.json). I have tried policies, but I face the following difficulties:
1) I couldn't pass the variable in the configuration from the controller to a parameterized authorize attribute.
2) I couldn't inject the configuration into a parameterized authorize attribute.
This effectively eliminated my ability to follow this guide in any way: https://learn.microsoft.com/en-us/aspnet/core/security/authorization/iauthorizationpolicyprovider?view=aspnetcore-2.2
This leads to my question: How can I use a value from appSettings.json to override whether the request checks a role or not?

After a lot of research, I found a way to do it using TypeFilterAttribute. Essentially, it's the same way of doing it (using a custom attribute to filter all requests and check the condition within the custom attribute) except I used .net Core-supported methods.
In case you are trying to solve this same issue, here are the exact steps to my solution.
Add two files, "YourAttributeNameAttribute.cs" and "YourFilterNameFilter.cs".
In the "YourAttributeNameAttribute.cs" file, the code is as follows:
public class YourAttributeNameAttribute : TypeFilterAttribute
{
public YourAttributeNameAttribute(string role) : base(typeof(YourFilterNameFilter))
{
Arguments = new object[] { role };
}
}
The code in "YourFilterNameFilter.cs":
public class YourFilterNameFilter : IAuthorizationFilter
{
private readonly string Role;
public YourFilterNameFilter(string role)
{
Role = role;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var configuration = context.HttpContext.RequestServices.GetService<IConfiguration>();
// If private server, ignore roles
if (private_server_logic_here)
return;
var user = context.HttpContext.User;
// Check role if on public server
if (!user.IsInRole(Role))
{
context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Unauthorized);
return;
}
}
}
The code for the controller:
[YourAttributeName("role_name")]
[Route("api/my_route")]
[HttpGet]

Related

Custom Authorization Filtering Minimal API .Net 6

I'm exploring Minimal APIs in .Net 6, and trying to apply a custom Authorization Filter to the endpoint (via Attributes or Extensions).
But it seems to me, I am doing something wrong, or it's simply not designed to work in that way (and it's sad if so).
Couldn't find anything in the docs besides the default usage of [Authorize] attribute in Minimal APIs.
Here is the Filter
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomAuthorizeAttribute : Attribute, IAuthorizationFilter
{
//Checking tokens
}
And if I try to apply it at Controller level, it works fine
[CustomAuthorize]
public class CustomController : ControllerBase
{
//Necessary routing
}
But if I switch to Minimap APIs notation and try to use attributes
app.MapGet("/customEndpoint",
[CustomAuthorize] async ([FromServices] ICustomService customService, Guid id) =>
await customService.GetCustomStuff(id));
or even an extension method
app.MapGet("/customEndpoint",
async ([FromServices] ICustomService customService, Guid id) =>
await customService.GetCustomStuff(id)).WithMetadata(new CustomAuthorizeAttribute());
It just doesn't work. The filter doesn't even being constructed.
What did I miss or did wrong?
Thx in advance
You can write a custom authorization filter for Minimal API in .NET 6.0
Here is how I tend to approach it - by using Policy-based authorization in ASP.NET Core
Step 1: Create a Requirement
A requirement implements IAuthorizationRequirement
public class AdminRoleRequirement : IAuthorizationRequirement
{
public AdminRoleRequirement(string role) => Role = role;
public string Role { get; set; }
}
Note: A requirement doesn't need to have data or properties.
Step 2: Create a Requirement Handler
A requirement handler implements AuthorizationHandler<T>
public class AdminRoleRequirementHandler : AuthorizationHandler<AdminRoleRequirement>
{
public AdminRoleRequirementHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
{
if (context.User.HasClaim(c => c.Value == requirement.Role))
{
context.Succeed(requirement);
}
else
{
_httpContextAccessor.HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
_httpContextAccessor.HttpContext.Response.ContentType = "application/json";
await _httpContextAccessor.HttpContext.Response.WriteAsJsonAsync(new { StatusCode = StatusCodes.Status401Unauthorized, Message = "Unauthorized. Required admin role." });
await _httpContextAccessor.HttpContext.Response.CompleteAsync();
context.Fail();
}
}
private readonly IHttpContextAccessor _httpContextAccessor;
}
Note: HandleRequirementAsync method returns no value. The status of either success or failure is indicated by calling context.Succeed(IAuthorizationRequirement requirement) and passing the requirement that has been successfully validated or by calling context.Fail() to indicate AuthorizationHandlerContext.HasSucceeded will never return true, even if all requirements are met.
Step 3: Configure Your Policy in the Authorization Service
builder.Services.AddAuthorization(o =>
{
o.AddPolicy("AMIN", p => p.AddRequirements(new AdminRoleRequirement("AMIN")));
});
Step 4: Add Your Requirement Handler to DI
builder.Services.AddSingleton<IAuthorizationHandler, AdminRoleRequirementHandler>();
Step 5: Apply Policy to Endpoints
app.MapGet("/helloworld", () => "Hello World!").RequireAuthorization("AMIN");
I think you won't be able to inject action filter in minimal api, you can use 3 alternative approches.
Create a custom middleware and inject it in startup class, it would check every request and do the intended work as you filter is doing. You can put a check for the request path there if you only need to validate a specific controller/endpoint.
The second approach is you can inject httpcontext in minimal api like this, from that extract jwt token and validate that, if found not ok reject that request.
app.MapGet("/customEndpoint", async (HttpContext context, ICustomService service) =>
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (string.isNullOrEmpty(token) || <not a valid token>) return Results.Unauthorized();
// do some work
return Results.Ok(result);
});
as #Dai suggested, you can extract token in this way also
AuthenticationHeaderValue.TryParse(context.Request.Headers["Authorization"], out var parsed ) && parsed.Scheme == "BearerOrWhatever" ? parsed.Parameter : null
You can register the filter globally from startup.cs.

Custom Authorize attribute role is not working web api authentication

I'm facing an issue while working with web api azure ad authentication
I'm having controller like below, the one which having giving proper response, But the one which having customauthorization roles throwing error as "Authentication has been for this request".
[RoutePrefix("api/hospitals")]
public class hospitals : ApiController
{
[Route("GetAll")]
[HttpGet]
[Authorize]
public async Task<IEnumerable<Hospitals>> GetAll()
{
// return ok;
}
[Route("Getbeds")]
[HttpGet]
[SmAuthorize(Constants.Roles.Admin,
Constants.Roles.HotSpitalAdmin,
Constants.Roles.QA)]
public async Task<IEnumerable<Hospitals>> Getbeds()
{
// return ok;
}
}
The Getbeds method is throwing an error as "Authorization has been request".
Please find me Custom attribute class as well
public class SmAuthorizeAttribute : AuthorizeAttribute
{
public SmAuthorizeAttribute(params string[] roles)
{
this.Roles = string.Join(",", roles.Select(s => s.Trim()).ToArray());
}
}
Can anyone help on this ?
You can refer to this SO question's answer by Derek Greer for Dot Net core, additionally I will reiterate the answer below -
The approach recommended by the ASP.Net Core team is to use the new policy design which is fully documented here. The basic idea behind the new approach is to use the new [Authorize] attribute to designate a "policy" (e.g. [Authorize( Policy = "YouNeedToBe18ToDoThis")] where the policy is registered in the application's Startup.cs to execute some block of code (i.e. ensure the user has an age claim where the age is 18 or older).
The policy design is a great addition to the framework and the ASP.Net Security Core team should be commended for its introduction. That said, it isn't well-suited for all cases. The shortcoming of this approach is that it fails to provide a convenient solution for the most common need of simply asserting that a given controller or action requires a given claim type. In the case where an application may have hundreds of discrete permissions governing CRUD operations on individual REST resources ("CanCreateOrder", "CanReadOrder", "CanUpdateOrder", "CanDeleteOrder", etc.), the new approach either requires repetitive one-to-one mappings between a policy name and a claim name (e.g. options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));), or writing some code to perform these registrations at run time (e.g. read all claim types from a database and perform the aforementioned call in a loop). The problem with this approach for the majority of cases is that it's unnecessary overhead.
While the ASP.Net Core Security team recommends never creating your own solution, in some cases this may be the most prudent option with which to start.
The following is an implementation which uses the IAuthorizationFilter to provide a simple way to express a claim requirement for a given controller or action:
public class ClaimRequirementAttribute : TypeFilterAttribute
{
public ClaimRequirementAttribute(string claimType, string claimValue) : base(typeof(ClaimRequirementFilter))
{
Arguments = new object[] {new Claim(claimType, claimValue) };
}
}
public class ClaimRequirementFilter : IAuthorizationFilter
{
readonly Claim _claim;
public ClaimRequirementFilter(Claim claim)
{
_claim = claim;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var hasClaim = context.HttpContext.User.Claims.Any(c => c.Type == _claim.Type && c.Value == _claim.Value);
if (!hasClaim)
{
context.Result = new ForbidResult();
}
}
}
[Route("api/resource")]
public class MyController : Controller
{
[ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")]
[HttpGet]
public IActionResult GetResource()
{
return Ok();
}
}
Part of this answer for .NET Framework-
Recommended Custom Attribute class:
public class CustomAuthorize : System.Web.Http.AuthorizeAttribute
{
private readonly PermissionAction[] permissionActions;
public CustomAuthorize(PermissionItem item, params PermissionAction[] permissionActions)
{
this.permissionActions = permissionActions;
}
protected override Boolean IsAuthorized(HttpActionContext actionContext)
{
var currentIdentity = actionContext.RequestContext.Principal.Identity;
if (!currentIdentity.IsAuthenticated)
return false;
var userName = currentIdentity.Name;
using (var context = new DataContext())
{
var userStore = new UserStore<AppUser>(context);
var userManager = new UserManager<AppUser>(userStore);
var user = userManager.FindByName(userName);
if (user == null)
return false;
foreach (var role in permissionActions)
if (!userManager.IsInRole(user.Id, Convert.ToString(role)))
return false;
return true;
}
}
}

Alternative to ActionFilterAttribute in ASP.net core

I am trying to authenticate users in my asp.net core 2.2 application. This is how we use to do it in asp.net web api using .net 4.5
public class AuthFilter : ActionFilterAttribute, IActionFilter
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//based on certain condition
filterContext.Result = new UnauthorizedResult();
}
}
I am looing to achieve same in asp.net core 2.2. Only change is, I need to implement this logic only for few routes. So basically I need to check the route first and then authenticate users only for those roytes. This is what I found so far:
public class CustomAuthenticationSchemeProvider : AuthenticationSchemeProvider
{
private readonly IHttpContextAccessor httpContextAccessor;
public CustomAuthenticationSchemeProvider(
IHttpContextAccessor httpContextAccessor,
IOptions<AuthenticationOptions> options)
: base(options)
{
this.httpContextAccessor = httpContextAccessor;
}
private async Task<AuthenticationScheme> GetRequestSchemeAsync()
{
var request = httpContextAccessor.HttpContext?.Request;
if (request == null)
{
throw new ArgumentNullException("The HTTP request cannot be retrieved.");
}
// For API requests, use authentication tokens.
if (request.Path.StartsWithSegments("/api"))
{
return await GetSchemeAsync(OAuthValidationDefaults.AuthenticationScheme);
}
// For the other requests, return null to let the base methods
// decide what's the best scheme based on the default schemes
// configured in the global authentication options.
return null;
}
}
I am not sure how to implement this "GetSchemeAsync". I am also not sure what I am trying to achieve is the right way or not
Use the Authorize attribute on your controllers action where your need to authorize a user read the doc
If you it want to be more global, you can use an Authorization filter

How to get all the attribute routing collections in asp core web api 2.0?

I need to get all the attribute route collections. to validate against my list of scopes configured in appsettigs whether the user has got the access.
I am trying to include the validation as part of the Authentication.
[HttpGet("test/policymethod")]
public IActionResult PolicyMethod()
{
return Ok();
}
[HttpGet("test/check")]
public IActionResult CheckWithAuthentication()
{
var services = HttpContext.RequestServices.GetAllService<IAuthenticationService>();
return Ok();
}
How would I get above Route details?. I know we can do it using "AuthorizationHandler".However, in my case I need a different approach.
Any other option to retrieve all the Route values in asp core web api 2.0?
This is something I achieved using AuthorizationHandler and action names.I need
route collection or action names without using the AuthorizationHandler.
I am trying to include the validation as part of the Authentication. Please suggest a workaround.
public class ScopeAuthorization : IAuthorizationRequirement
{
public IEnumerable<string> Scopes { get; }
public ScopeAuthorization(IEnumerable<string> scopes)
{
Scopes = scopes ?? throw new ArgumentNullException(nameof(scopes));
}
}
public class ScopeAuthorizationHandler : AuthorizationHandler<ScopeAuthorization>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ScopeAuthorization requirement)
{
var resContext = context.Resource as AuthorizationFilterContext;
if (resContext?.ActionDescriptor is ControllerActionDescriptor descriptor)
{
if (requirement.Scopes.Contains(descriptor.ActionName))
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}

How to implement Permission Based Access Control with Asp.Net Core

I am trying to implement permission based access control with aspnet core. For dynamically managing user roles and permissions(create_product, delete_product etc.), they are stored in the database. Data Model is like http://i.stack.imgur.com/CHMPE.png
Before aspnet core (in MVC 5) i was using custom AuthorizeAttribute like below to handle the issue:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private readonly string _permissionName { get; set; }
[Inject]
public IAccessControlService _accessControlService { get; set; }
public CustomAuthorizeAttribute(string permissionName = "")
{
_permissionName = permissionName;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
var user = _accessControlService.GetUser();
if (PermissionName != "" && !user.HasPermission(_permissionName))
{
// set error result
filterContext.HttpContext.Response.StatusCode = 403;
return;
}
filterContext.HttpContext.Items["CUSTOM_USER"] = user;
}
}
Then i was using it in action method like below:
[HttpGet]
[CustomAuthorize(PermissionEnum.PERSON_LIST)]
public ActionResult Index(PersonListQuery query){ }
Additionally, i was using HttpContext.Items["CUSTOM_USER"] in views to show or hide html part:
#if (CurrentUser.HasPermission("<Permission Name>"))
{
}
When i decided to switch aspnet core, all my plan was failed. Because there was no virtual OnAuthorization method in the AuthorizeAttribute. I tried some ways to solve problem. Those are below:
Using new policy based authorization(i think it is not suitable for
my scenerio)
Using custom AuthorizeAttribute and AuthorizationFilter(i read this
post https://stackoverflow.com/a/35863514/5426333 but i couldn’t change it properly)
Using custom middleware(how to get AuthorizeAttribute of current
action?)
Using ActionFilter(is it correct for security purpose?)
I couldn’t decide which way is the best for my scenerio and how to implement it.
First question: Is MVC5 implementation bad practice?
Second question: Do you have any suggest to implement aspnet core?
Based on the comments, here an example on how to use the policy based authorization:
public class PermissionRequirement : IAuthorizationRequirement
{
public PermissionRequirement(PermissionEnum permission)
{
Permission = permission;
}
public PermissionEnum Permission { get; }
}
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly IUserPermissionsRepository permissionRepository;
public PermissionHandler(IUserPermissionsRepository permissionRepository)
{
if(permissionRepository == null)
throw new ArgumentNullException(nameof(permissionRepository));
this.permissionRepository = permissionRepository;
}
protected override void Handle(AuthorizationContext context, PermissionRequirement requirement)
{
if(context.User == null)
{
// no user authorizedd. Alternatively call context.Fail() to ensure a failure
// as another handler for this requirement may succeed
return null;
}
bool hasPermission = permissionRepository.CheckPermissionForUser(context.User, requirement.Permission);
if (hasPermission)
{
context.Succeed(requirement);
}
}
}
And register it in your Startup class:
services.AddAuthorization(options =>
{
UserDbContext context = ...;
foreach(var permission in context.Permissions)
{
// assuming .Permission is enum
options.AddPolicy(permission.Permission.ToString(),
policy => policy.Requirements.Add(new PermissionRequirement(permission.Permission)));
}
});
// Register it as scope, because it uses Repository that probably uses dbcontext
services.AddScope<IAuthorizationHandler, PermissionHandler>();
And finally in the controller
[HttpGet]
[Authorize(Policy = PermissionEnum.PERSON_LIST.ToString())]
public ActionResult Index(PersonListQuery query)
{
...
}
The advantage of this solution is that you can also have multiple handlers for a requirement, i.e. if first one succeed the second handler can determine it's a fail and you can use it with resource based authorization with little extra effort.
The policy based approach is the preferred way to do it by the ASP.NET Core team.
From blowdart:
We don't want you writing custom authorize attributes. If you need to do that we've done something wrong. Instead you should be writing authorization requirements.
I had same requirement and i have done it as below and it works fine for me. I am using .Net Core 2.0 Webapi
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Method
, AllowMultiple = true
, Inherited = true)]
public class CheckAccessAttribute : AuthorizeAttribute, IAuthorizationFilter
{
private string[] _permission;
public CheckAccessAttribute(params string[] permission)
{
_permission = permission;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = context.HttpContext.User;
if (!user.Identity.IsAuthenticated)
{
return;
}
IRepository service =
(IRepositoryWrapper)context.HttpContext.RequestServices.GetService(typeof(IRepository));
var success = service.CheckAccess(userName, _permission.ToList());
if (!success)
{
context.Result = JsonFormatter.GetErrorJsonObject(
CommonResource.error_unauthorized,
StatusCodeEnum.Forbidden);
return;
}
return;
}
}
In Controller use it like below
[HttpPost]
[CheckAccess(Permission.CreateGroup)]
public JsonResult POST([FromBody]Group group)
{
// your code api code here.
}
For a solution that doesn't require you to add a policy for each permission see my answer for another question.
It lets you decorate your Controllers and Actions with any custom attributes you wish, and access them in your AuthorizationHandler.

Categories

Resources