Disclaimer: Results for this topic are typically very old and do not necessarily refer to Asp.Net Core 3.1 using Identity Core. I am trying to find a modern recommended solution.
I have created a proof of concept Web API application using Identity and JWT. The idea is to navigate to the Web Site and be able to log in and view your claims, change your password and other features that come with Identity.
The other component is to use the Web API with JWT only.
I've implemented claims/policies and the authorization is working very well. Overall I am happy with this proof of concept.
One thing, in particular, leaves much to be desired: the messages when a user is not authenticated, or authenticated but not authorized when consuming the API with JWT. At the moment all it does is return a 403 (authenticated but not authorized) or 401 (not authenticated) response code with no relevant messages.
Take this example. I have a claim, ClaimType: Api, ClaimValue: Delete
mapped to a policy (string constants to avoid magic strings): CanDeletePolicy
options.AddPolicy(Policies.CanDeletePolicy, policy => {
policy.RequireClaim("Api", "Delete");
});
Keep in mind, everything is working as intended. I am just looking to provide the consumer of the API with meaningful messages.
The Controller:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ApiController]
[Route("[controller]")]
[Produces("application/json")]
public class PeopleController : ControllerBase {
private readonly ILogger<PeopleController> _logger;
private readonly IPeopleService _peopleService;
public PeopleController(ILogger<PeopleController> logger, IPeopleService peopleService) {
_logger = logger;
_peopleService = peopleService;
}
...
The Action within the PeopleController:
[HttpDelete("[action]/{id:int}")]
[Authorize(Policy = Policies.CanDeletePolicy)]
public IActionResult Delete(int id) {
var result = _peopleService.Delete(id);
var username = User.FindFirstValue(ClaimTypes.Name);
if (result < 1) {
var message = $"User '{username}' was unable to delete a person with Id {id}";
_logger.LogError(message);
return BadRequest(new {
Message = message
});
}
var successMessage = $"User '{username}' successfully deleted the person with Id {id}";
_logger.LogDebug(successMessage);
return Ok(new {
Message = "Person deleted"
});
}
If the user attempts to use this action:
I would like to provide varying degrees of information:
401 with the response body "You are not authenticated"
403 with the response body "You are not authorized" or "You are not authorized: Api, Update required"
What are my options? Creating a custom Authorize attribute? I'd rather not reinvent the wheel just to add a simple message. I am also hoping to make the authorization responses in JSON format, but I don't think that matters for a proposed solution.
public class ResponseFormatterMiddleware
{
private readonly RequestDelegate _next;
public ResponseFormatterMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
await _next.Invoke(context);
if (context.Response.StatusCode == StatusCodes.Status401Unauthorized)
{
await context.Response.WriteAsync(
JsonConvert.SerializeObject(new ResponseModel("some-message")));
}
}
}
public class ResponseModel
{
public ResponseModel(string message)
{
this.Message = message;
}
public string Message { get; set; }
}
And in the startup :
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// put middleware before authentication
app.UseMiddleware<ResponseFormatterMiddleware>();
app.UseAuthentication();
}