If I use [Authorize(Roles="xxx")] on my controller method, it does what its supposed to, but it returns a 403. Is there any way to make it return a 401? I found some SO to do that but that's for regular .Net.
-
1https://stackoverflow.com/questions/38800919/how-to-return-401-instead-of-302-in-asp-net-core. This may help – jaabh Oct 12 '20 at 22:32
-
Technically, 401 is an *authentication* error, not authorization. 401 = *don't know who you are*, vs 403 = *you can't do this* – CoolBots Oct 12 '20 at 22:34
-
Why do you want to change the status code? 401 is for authentication and when a request owner has not the defined role it means that you know the user but you are forbidding him and you should return 403. 401 is for times that request has not a token or anything like this – Hadi Samadzad Oct 12 '20 at 22:35
-
@janzen that's specific to redirects... doesn't apply to authentication / authorization errors. – CoolBots Oct 12 '20 at 22:38
-
@HadiSamadzad - Due to stupid Azure AD behavior. login.microsoft.com will give you a token for ANY scope you ask for and for ANY credentials you use. Only by validating the role, can you block it. – SledgeHammer Oct 12 '20 at 22:39
-
@CoolBots See my response to Hadi. Since login.microsoft.com will give you a token for whatever scope you ask for as long as the credentials are valid, technically it IS an authentication error :). – SledgeHammer Oct 12 '20 at 22:41
-
I see... that's an unfortunate behavior by Azure AD... is that by design? Meaning, is it something MS is aware of and have rationale for why it works that way? Seems to not fit the whole authentication/authorization cycle too well... In terms of handling it, it may be possible to implement a custom HttpHandler, intercept the response, and replace a 403 with a 401. – CoolBots Oct 12 '20 at 22:50
-
1@CoolBots We spent the past two days debugging it :). I agree, the behavior doesn't make sense to me. No matter what scope we asked for, login.microsoft.com would give us a token if it validated the credentials. The token would be missing the roles attribute though. I'd think a token should only be returned for scopes the client is authorized for... – SledgeHammer Oct 12 '20 at 22:54
1 Answers
You can write a custom middleware solution to handle this. Here's an example, however, this can be a rather expensive solution in terms of server resources, depending on what sort of responses are typically handled (files, for instance, are an issue with this approach), and how busy the server is. It may be possible to adapt to your needs with a reduced cost, but given the information I have from your question, I can only offer a general approach:
public class ReplaceHttpCode
{
private RequestDelegate nextDelegate;
private (int codeToReplace, int replaceWithCode) httpCodes;
public ReplaceHttpCode(RequestDelegate nextDelegate, int codeToReplace, int replaceWithCode)
=> (this.nextDelegate, httpCodes) = (nextDelegate, (codeToReplace, replaceWithCode));
public async Task Invoke(HttpContext context)
{
// Save the pointer to the actual client response stream
var clientResponseStream = context.Response.Body;
// re-point client response stream to a MemoryStream
// WARNING: This can be rather expensive in terms of server resources!
context.Response.Body = new MemoryStream();
// Pass the context to the next middleware in line
await nextDelegate.Invoke(context);
// ... and we're back! Check the status code
if(context.Response.StatusCode == httpCodes.codeToReplace)
{
// Update response code
context.Response.StatusCode = httpCodes.replaceWithCode;
}
else
{
// Copy response from MemoryStream
context.Response.Body.Seek(0, SeekOrigin.Begin);
await context.Response.Body.CopyToAsync(clientResponseStream);
}
// repoint back to original client response stream
context.Response.Body = clientResponseStream;
}
}
Usage:
You can add a helper extensions (recommended approach):
public static class ReplaceHttpCodeExtensions
{
public static IApplicationBuilder ReplaceHttpCode(this IApplicationBuilder builder,
int codeToReplace,
int replaceWithCode)
=> builder.UseMiddleware<ReplaceHttpCode>(codeToReplace, replaceWithCode);
}
and add the following somewhere in your StartUp.Configure(IApplicationBuilder app, IWebHostEnvironment env)
method:
app.ReplaceHttpCode(403, 401);
Make sure to insert it in the right place in the middleware hierarchy - if you have other middleware, you may need to play with that to figure out a good spot, with breakpoints. In my simple testing, I placed it near the top of the function, right after the exception handling configuration.
Again, I would recommend using this as a starting point, and seeing if you can handle authentication in such middleware approach, so that you can reduce or eliminate the large number of buffered response streams.

- 4,770
- 2
- 16
- 30