How to add custom error messages on authorization failures in .Net 5 Web API?

2.6k views Asked by At

I have a .Net 5 Web API that is protected by Azure AD. What I want to accomplish is to provide custom error message when authorization fails. For example,

  • If a user forgets to add Authorization header in the request, I want to tell the user that this header is required.
  • If the token has expired, I want to tell the user exactly that rather than simply returning 401 status code with no details.

Simply putting [Authorize] attribute before controller and/or action does not work as it returns 401 status code and does not include any details about this error.

I searched for it and found that I have to write a custom authorization filter and using that it should be possible to accomplish what I am looking for.

Using this blog post, I was able to write a custom authorization filter however I am still not able to find out how to validate the token and return appropriate error messages.

Here's the code I wrote so far:

Startup.cs

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        AuthorizationPolicy policy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme).RequireAuthenticatedUser().Build();
        _ = services.AddAuthorization(options =>
        {
            options.DefaultPolicy = policy;
        });
        _ = services.AddControllers(options =>
        {
            options.Filters.Add(new CustomTokenAuthorizationFilter(policy));
        });
        _ = services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" });
        });
        _ = services
            .AddMicrosoftIdentityWebApiAuthentication(Configuration, "ApiSettings");
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            _ = app.UseDeveloperExceptionPage();
            _ = app.UseSwagger();
            _ = app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1"));
        }


        _ = app.UseConfigureRequestHeaders();

        _ = app.UseHttpsRedirection();

        _ = app.UseRouting();

        //_ = app.UseAuthentication();

        _ = app.UseAuthorization();

        _ = app.UseEndpoints(endpoints =>
        {
            _ = endpoints.MapControllers();
        });
    }
}

CustomAuthorizationFilter.cs

public class CustomTokenAuthorizationFilter : AuthorizeFilter
{
    public CustomTokenAuthorizationFilter(AuthorizationPolicy policy) : base(policy)
    {

    }

    public override async Task OnAuthorizationAsync(AuthorizationFilterContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        // Allow Anonymous skips all authorization
        if (context.Filters.Any(item => item is IAllowAnonymousFilter))
        {
            return;
        }
        var policyEvaluator = context.HttpContext.RequestServices.GetRequiredService<IPolicyEvaluator>();
        var authenticateResult = await policyEvaluator.AuthenticateAsync(Policy, context.HttpContext);
        var authorizeResult = await policyEvaluator.AuthorizeAsync(Policy, authenticateResult, context.HttpContext, context);
        return;
    }
}

With this code, when there's an issue with my token authenticateResult.Succeeded property is false and authorizeResult.Challenged property is true.

How can I find out what caused the authorization to fail? Any help would be highly appreciated.

I am using Microsoft.Identity.Web NuGet Package as suggested by Microsoft.

1

There are 1 answers

0
845614720 On

What you could possibly do here and user an error controller and catch your unauthorized exception and then output the message from that exception.

This is a good Stackoverflow post about how to configure an error controller