How to change status code & add message from failed AuthorizationHandler policy
I have achieved it using ExceptionHandler. I wanted to keep 403 status code, but to customize error message. So I have created custom Exception:
public class ForbiddenException : BaseException
{
public ForbiddenException(string message) : base(HttpStatusCode.Forbidden, message)
{
}
public ForbiddenException(string message, object info) : base(HttpStatusCode.Forbidden, message, info)
{
}
}
public abstract class BaseException : Exception
{
protected BaseException(HttpStatusCode httpErrorCode, string message, Exception innerException = null) : base(message,
innerException)
{
HttpErrorCode = httpErrorCode;
}
protected BaseException(HttpStatusCode httpErrorCode, string message, object info, Exception innerException = null) :
this(httpErrorCode,
message, innerException)
{
Info = info;
}
public HttpStatusCode HttpErrorCode { get; set; }
public object Info { get; set; }
}
Then inside AuthorizationHandler I threw this exception:
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
List<Claim> userPermissionsClaims =
context.User.Claims.Where(c => c.Type == PermissionConstants.PermissionClaimType).ToList();
if (userPermissionsClaims.Any(pc => requirement.Permissions.Any(p => (Permissions)Convert.ToInt16(pc.Value) == p)))
{
context.Succeed(requirement);
}
else
{
throw new ForbiddenException(string.Format(ErrorMessages.PermissionsRequired, string.Join(", ", requirement.Permissions)));
}
return Task.CompletedTask;
}
}
Here is a global exception handler:
app.UseExceptionHandler(exceptionHandler =>
{
exceptionHandler.Run(async context =>
{
await HandleException(context.Features.Get<IExceptionHandlerFeature>().Error, context, env);
});
});
public async Task HandleException(Exception exception, HttpContext context)
{
var message = exception?.Message;
if (exception is BaseException)
{
context.Response.StatusCode = (int)(exception as BaseException)?.HttpErrorCode;
}
else
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
}
await context.Response.WriteAsync(message);
await context.Response.CompleteAsync();
}
context.Resource as AuthorizationFilterContext is null in net core 3.1
Finally I rewrite the method to this:
public class SysUserAuthHandler : AuthorizationHandler<SysUserAuthRequirement> {
private readonly IFetchLoginUser fetchUser;
private readonly IHttpContextAccessor httpContextAccessor;
public SysUserAuthHandler( IFetchLoginUser fetchLoginUser, IHttpContextAccessor httpContextAccessor ) {
fetchUser = fetchLoginUser;
this.httpContextAccessor = httpContextAccessor;
}
protected override Task HandleRequirementAsync( AuthorizationHandlerContext context, SysUserAuthRequirement requirement ) {
var httpContext = httpContextAccessor.HttpContext;
byte[] bytes;
string msg;
if (!string.IsNullOrWhiteSpace( context.User.Identity.Name )) {
var myUser = fetchUser.LoadUser( context.User.Identity.Name, SystemEnum.FooSytem);
if ((myUser.Auth & requirement.Auth) == requirement.Auth) {
context.Succeed( requirement );
return Task.CompletedTask;
}
msg = requirement.Auth switch {
1 => "You don't have Auth of Maker",
2 => "You don't have Auth of Checker",
4 => "You don't have Auth of Admin",
8 => "You don't have Auth of Operator",
_ => "You don't have Auth"
};
}
else {
msg = "User Invalid, Please check your login status or login again";
}
bytes = Encoding.UTF8.GetBytes( msg );
httpContext.Response.StatusCode = 405;
httpContext.Response.ContentType = "application/json";
httpContext.Response.Body.WriteAsync( bytes, 0, bytes.Length );
//context.Succeed( requirement );
return Task.CompletedTask;
}
}
public class SysUserAuthRequirement : IAuthorizationRequirement {
public long Auth { get; private set; }
public SysUserAuthRequirement( long auth ) {
Auth = auth;
}
}
Do not forget add this line in Startup
services.AddHttpContextAccessor();
The documentation for Customize the behavior of AuthorizationMiddleware can be found below:
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/customizingauthorizationmiddlewareresponse?view=aspnetcore-5.0
My code finally looked like this:
public class GuidKeyAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
private readonly AuthorizationMiddlewareResultHandler
DefaultHandler = new AuthorizationMiddlewareResultHandler();
public async Task HandleAsync(
RequestDelegate requestDelegate,
HttpContext httpContext,
AuthorizationPolicy authorizationPolicy,
PolicyAuthorizationResult policyAuthorizationResult)
{
if (policyAuthorizationResult.Challenged && !policyAuthorizationResult.Succeeded && authorizationPolicy.Requirements.Any(requirement => requirement is GuidKeyRequirement))
{
httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}
// Fallback to the default implementation.
await DefaultHandler.HandleAsync(requestDelegate, httpContext, authorizationPolicy,
policyAuthorizationResult);
}
}
Startup.cs:
services.AddSingleton<IAuthorizationMiddlewareResultHandler,
GuidKeyAuthorizationMiddlewareResultHandler>();