How to handle authorization error after migrated from ASP.NET Core 2.1 to .NET 6? - authorization

I have migrated my project from asp.net core 2.1 to .NET 6, and now I am facing an error with
context.Resource as AuthorizationFilterContext which is return NULL.
I have implemented a custom Policy-based Authentication using AuthorizationFilterContext, It seems that.NET 6 do not support AuthorizationFilterContext Please help me how to modify the below code from asp.net core 2.1 to .NET6. thank you.
Here is the error message in this line var mvcContext = context.Resource as AuthorizationFilterContext;
mvcContext == NULL
Here is the Implemention Code of AuthorizationHandler and AuthorizationHandlerContext
public class HasAccessRequirment : IAuthorizationRequirement { }
public class HasAccessHandler : AuthorizationHandler<HasAccessRequirment>
{
public readonly HoshmandDBContext _context;
public HasAccessHandler(HoshmandDBContext context)
{
_context = context;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HasAccessRequirment requirement)
{
Contract.Ensures(Contract.Result<Task>() != null);
List<int?> userGroupIds = new List<int?>();
// receive the function informations
var mvcContext = context.Resource as AuthorizationFilterContext;
if ((mvcContext != null) && !context.User.Identity.IsAuthenticated)
{
mvcContext.Result = new RedirectToActionResult("UserLogin", "Logins", null);
return Task.FromResult(Type.Missing);
}
if (!(mvcContext?.ActionDescriptor is ControllerActionDescriptor descriptor))
{
return Task.FromResult(Type.Missing);
}
var currntActionAddress = descriptor.ControllerName + "/" + descriptor.ActionName;
// finding all information about controller and method from Tables
// check user has access to current action which is being called
//allActionInfo = ListAcctionsFromDatabase;
//bool isPostBack = allActionInfo.FirstOrDefault(a => a.action == currntActionAddress)?.IsMenu ?? true;
bool isPostBack = false;
if (!isPostBack)
{
mvcContext.Result = new RedirectToActionResult("AccessDenied", descriptor.ControllerName, null);
context.Succeed(requirement);
return Task.CompletedTask;
}
else
{
mvcContext.Result = new RedirectToActionResult("AccessDeniedView", descriptor.ControllerName, null);
context.Succeed(requirement);
return Task.CompletedTask;
}
}
}
Here is my Program.cs Code:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("HasAccess", policy => policy.AddRequirements(new HasAccessRequirment()));
});
builder.Services.AddTransient<IAuthorizationHandler, HasAccessHandler>();
Here is the Controller Code:
[Authorize(policy: "HasAccess")]
public class HomeController : BaseController
{
}

There are some changes since .net core 3 about AuthorizationFilterContext:
A. MVC is no longer add AuthorizeFilter to ActionDescriptor and ResourceInvoker won’t call AuthorizeAsync().
B. It will add Filter as metadata to endpoint. Also, in .net 5 it changed the context.Resource as the type of DefaultHttpContext.
So here is the new method:
public class MyAuthorizationPolicyHandler : AuthorizationHandler<OperationAuthorizationRequirement>
{
public MyAuthorizationPolicyHandler()
{
}
protected async override Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement)
{
var result = false;
if (context.Resource is Microsoft.AspNetCore.Http.DefaultHttpContext httpContext)
{
var endPoint = httpContext.GetEndpoint();
if (endPoint != null)
{
var attributeClaims = endPoint.Metadata.OfType<MyAuthorizeAttribute>()
//TODO: Add your logic here
}
if (result)
{
context.Succeed(requirement);
}
}
}
Please refer to this discussion: "context.Resource as AuthorizationFilterContext" returning null in ASP.NET Core 3.0

Related

ASP.NET Core 5.0 Customizing Authorization

I implemented an authorization handler to grab the area, controller, and action then validating the user’s access.
public class PermissionsAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly ISecurityTrimmingService _securityTrimmingService;
public PermissionsAuthorizationHandler(ISecurityTrimmingService securityTrimmingService)
{
_securityTrimmingService = securityTrimmingService;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
if (context.Resource is RouteEndpoint endpoint)
{
endpoint.RoutePattern.RequiredValues.TryGetValue("controller", out var _controller);
var controller = _controller.ToString();
endpoint.RoutePattern.RequiredValues.TryGetValue("action", out var _action);
var action = _action.ToString();
endpoint.RoutePattern.RequiredValues.TryGetValue("area", out var _area);
var area = _area.ToString();
var isAuthenticated = context.User.Identity.IsAuthenticated;
if (isAuthenticated && _controller != null && _action != null && _securityTrimmingService.CanCurrentUserAccess(area, controller, action))
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
It worked properly in ASP.NET Core 3.1, but there is a problem with version 5.0:
RoutePattern doesn't contain a definition for RequiredValues...
Change the if condition, use HttpContext instead of RouteEndpoint.
if (context.Resource is HttpContext httpContext)
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DynamicPermissionRequirement requirement)
{
if (context.Resource is HttpContext httpContext)
{
httpContext.GetRouteData().Values.TryGetValue("controller", out var _controller);
var controller = _controller.ToString();
httpContext.GetRouteData().Values.TryGetValue("action", out var _action);
var action = _action.ToString();
httpContext.GetRouteData().Values.TryGetValue("area", out var _area);
var area = _area.ToString();
var isAuthenticated = context.User.Identity.IsAuthenticated;
if (isAuthenticated && _controller != null && _action != null && _securityTrimmingService.CanCurrentUserAccess(area, controller, action))
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}

Check User Authorization with Aspect Oriented Programming in Asp.Net Core 3.1 Web Api

I want to check user authorization in the api method.
Method responsible for get for an employee by id. So user should render this method if the user works the same company with employee. So I mean user CompanyId should be same with the Employee CompanyId.
Think about like this api method:
public async Task<IActionResult> GetEmployeeById([FromRoute]int id)
{
try
{
var entity = await _employeeRepository.GetAsync(p => p.Id == id);
if (entity == null)
{
return NotFound();
}
//user's CompanyId should be same with Employee CompanyId
var user = await _userManager.FindByIdAsync(User.Identity.Name);
if (user.CompanyId != eployee.CompanyId)
{
return Unauthorized();
}
return Ok(entity);
}
catch (Exception ex)
{
throw ex;
}
}
I have to check user company in all api methods.
So I want to do this by Aspect Oriented Programming.So method should be like this after AOP implementation:
[CheckUserCompany]
public async Task<IActionResult> GetEmployeeById([FromRoute]int id)
{
try
{
var entity = await _employeeRepository.GetAsync(p => p.Id == id);
if (entity == null)
{
return NotFound();
}
return Ok(entity);
}
catch (Exception ex)
{
throw ex;
}
}
How can I do this with Aspect Oriented Programming in Asp.Net Core 3.1 Web Api?
Thanks
You could customize a ActionFilter like below:
public class CheckUserCompany:IActionFilter
{
private readonly ApplicationDbContext _dbcontext;
private readonly UserManager<ApplicationUser> _userManager;
public CheckUserCompany(ApplicationDbContext dbcontext, UserManager<ApplicationUser> userManager)
{
_dbcontext = dbcontext;
_userManager = userManager;
}
// Do something before the action executes.
public void OnActionExecuting(ActionExecutingContext context)
{
var id = (int)context.ActionArguments["id"];
var employee = _dbcontext.Employee.Find(id);
var user = _userManager.FindByNameAsync(context.HttpContext.User.Identity.Name);
if (user.Result.CompanyId != employee.CompanyId)
{
throw new UnauthorizedAccessException();
}
return;
}
// Do something after the action executes.
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
If we want to use our filter as a service type on the Action or Controller level, we need to register it in the same ConfigureServices method but as a service in the IoC container:
services.AddScoped<CheckUserCompany>();
Finally, to use a filter registered on the Action or Controller level, we need to place it on top of the Controller or Action as a ServiceType:
[ServiceFilter(typeof(CheckUserCompany))]
public async Task<IActionResult> GetEmployeeById([FromRoute]int id)

.NET Core Audit Logging

I want to implement audit logging in my .NET Core application.
Something like
[HttpPost, Auditing]
public dynamic SomeApiAction()
{
// API code here
...
}
The Attribute should be able to intercept the API call before and after execution in order to log.
Is there any such mechanism available in .net core as a part of the framework? I don't want to use any third-party component.
Please advise.
You can try Audit.WebApi library which is part of Audit.NET framework. It provides a configurable infrastructure to log interactions with your Asp.NET Core Web API.
For example using attributes:
using Audit.WebApi;
public class UsersController : ApiController
{
[HttpPost]
[AuditApi(IncludeHeaders = true)]
public IHttpActionResult Post()
{
//...
}
}
You can use CustomActionFilter for it like
public class CustomDemoActionFilter : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
var controller = context.Controller as Controller;
if (controller == null) return;
var controllerName = context.RouteData.Values["controller"];
var actionName = context.RouteData.Values["action"];
var message = String.Format("{0} controller:{1} action:{2}", "onactionexecuting", controllerName, actionName);
var CurrentUrl = "/" + controllerName + "/" + actionName;
bool IsExists = false;
if(CurrentUrl=="/Home/Index")
{
IsExists=true;
}
else
{
IsExists=false;
}
if (IsExists)
{
//do your conditional coding here.
//context.Result = new RedirectToRouteResult(new RouteValueDictionary { { "controller", "Home" }, { "action", "Index" } });
}
else
{
//else your error page
context.Result = new RedirectToRouteResult(new RouteValueDictionary { { "controller", "Home" }, { "action", "Error" } });
}
//base.OnActionExecuting(context);
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
and just use this customactionfilter as attribute over your action method like
[HttpGet]
[CustomHMISActionFilter]
public IActionResult Registration()
{
//your code here
}

Controller and Action from Global Error Handler

I'm migrating an web api from .Net to .NetCore.
We had a custom ExceptionFilterAttribute to handle errors in a centralized way. Something like this:
public class HandleCustomErrorAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext filterContext)
{
// Error handling routine
}
}
With some search, I managed to create something similar on .Net Core
public static class ExceptionMiddlewareExtensions
{
public static void ConfigureExceptionHandler(this IApplicationBuilder app)
{
app.UseExceptionHandler(appError =>
{
appError.Run(async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
if (contextFeature != null)
{
//logger.LogError($"Something went wrong: {contextFeature.Error}");
await context.Response.WriteAsync(new ErrorDetails()
{
StatusCode = context.Response.StatusCode,
Message = "Internal Server Error."
}.ToString());
}
});
});
}
}
I need to find a way to access these 3 info that where avaiable in .Net in .Net Core version:
filterContext.ActionContext.ActionDescriptor.ActionName;
filterContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerName;
HttpContext.Current.Request.Url.ToString();
Is it possible ?
For a complete solution with registering ExceptionFilter and get request path, you could try like
ExceptinoFilter
public class ExceptinoFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
string controllerName = context.RouteData.Values["controller"].ToString();
string actionName = context.RouteData.Values["action"].ToString();
var request = context.HttpContext.Request;
var requestUrl = request.Scheme + "://" + request.Host + request.Path;
}
}
Register
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options => {
options.Filters.Add(new ExceptinoFilter());
});
}

ASP.NET Core 2.0 Redirecting user from AuthorizationHandler, HandleRequirementAsync method

I am trying to implement AuthorizationHandler in .net core 2.0 where i need to authorize the user and based on the condition wanted to redirect to different action methods within my application validation works ok but how i can redirect user to the Access Denied or Login page when authorization failed.
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HasPermissionRequirement requirement)
{
var controllerContext = context.Resource as AuthorizationFilterContext;
if (sessionManager.Session.sysUserID <= 0)
{
controllerContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Account", action = "Login", area = "" }));
return Task.FromResult(0);
}
if (Utilities.GetInt32Negative(PermissionID) == 1 || Utilities.GetInt32Negative(PermissionID) == -1)
{
if (!PagePath.Equals("~/"))
controllerContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Home", action = "NoAccess", area = "" }));
}
context.Succeed(requirement);
}
else
{
if (!PagePath.Equals("~/"))
controllerContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Home", action = "NoAccess", area = "" }));
}
return Task.FromResult(0);
}
I found the solution and i hope this will help someone looking for the similar, in custom authorization we can redirect to any desired controller action using the AuthorizationFilterContext and with the RedirectToActionResult
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HasPermissionRequirement requirement)
{
// Get the context
var redirectContext = context.Resource as AuthorizationFilterContext;
//check the condition
if (!result)
{
redirectContext.Result = new RedirectToActionResult("AccessDenied", "Home", null);
context.Succeed(requirement);
return Task.CompletedTask;
}
context.Succeed(requirement);
return Task.CompletedTask;
}
First you can configure the conditions for login page/authentication by creating a custom scheme like this.
public class SampleScheme : AuthenticationHandler<AuthenticationSchemeOptions>
{
public const string SchemeName = "sample";
public SampleScheme(IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (myconditions){
return AuthenticateResult.Fail("error message");
}
else {
return await Context.AuthenticateAsync
(CookieAuthenticationDefaults.AuthenticationScheme);
// return default cookie functionality.
}
}
}
Then you can create a similar class for Access denied/forbidden access as well (lets say SampleScheme2).
Finally you can set them up in your startup.cs
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = SampleScheme.SchemeName;
options.DefaultForbidScheme = SampleScheme2.SchemeName;
})
.AddCookie(options => {
options.LoginPath = "/login";
options.AccessDeniedPath = "/forbidden";
})
.AddScheme<AuthenticationSchemeOptions, SampleScheme>(SampleScheme.SchemeName, o => { });
.AddScheme<AuthenticationSchemeOptions, SampleScheme2>(SampleScheme2.SchemeName, o => { });
I hope the code is self explanatory enough. There are some variations so let me know if this is not exactly what you were going for.
Related to #Azhar's answer, but applicable for .NET 6.
It seems that AuthorizationHandlerContext.Resource no longer pattern matches to AuthorizationFilterContext. A runtime error reveals that the Type is now DefaultHttpContext. Following this, the Redirect(string, bool) method on the HttpResponse can be used for AuthorizationHandler-specific routing.
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HasPermissionRequirement requirement)
{
//Get the context
var httpContext = context.Resource as DefaultHttpContext;
//check the condition
if (!result)
{
//Set redirect
httpContext.Response.Redirect("/Home/AccessDenied", permanent: true);
context.Succeed(requirement);
return Task.CompletedTask;
}
context.Succeed(requirement);
return Task.CompletedTask;
}
References:
HttpContext.Redirect(string, bool)
HttpContext.Redirect examples