118

I have an MVC webapi site that uses OAuth/token authentication to authenticate requests. All the relevant controllers have the right attributes, and authentication is working ok.

The problem is that not all of the request can be authorised in the scope of an attribute - some authorisation checks have to be performed in code that is called by controller methods - what is the correct way to return a 401 unauthorised response in this case?

I have tried throw new HttpException(401, "Unauthorized access");, but when I do this the response status code is 500 and I get also get a stack trace. Even in our logging DelegatingHandler we can see that the response is 500, not 401.

LukeH
  • 242,140
  • 52
  • 350
  • 400
GoatInTheMachine
  • 3,155
  • 3
  • 22
  • 31
  • 3
    To anyone picking up this answer down the line, I'd suggest thinking about the appropriate time to throw a `HttpResponseException` versus when to return an `Unauthorized()`. Using the exception for an 'expected' error is a bit of an anti-pattern, so if there are cases you expect the call to make this mistake, returning `Unauthorized()` is probably the right call. Save `HttpResponseException` for the truly unexpected. – Rikki Sep 17 '18 at 18:06
  • See https://github.com/aspnet/Mvc/issues/5507 for some discussion. – Rikki Sep 17 '18 at 18:07
  • @Rikki, 401 is not an "expected" error. -- It's an exceptional circumstance that should cause you to abort your workflow (except maybe for logging, which you should already be doing for any exception...) -- Anyway, if you want to return a strong typed result from your controller (e.g. for ease of unit testing), an Exception is clearly the best route. – BrainSlugs83 Oct 09 '18 at 20:58

8 Answers8

156

You should be throwing a HttpResponseException from your API method, not HttpException:

throw new HttpResponseException(HttpStatusCode.Unauthorized);

Or, if you want to supply a custom message:

var msg = new HttpResponseMessage(HttpStatusCode.Unauthorized) { ReasonPhrase = "Oops!!!" };
throw new HttpResponseException(msg);
LukeH
  • 242,140
  • 52
  • 350
  • 400
  • 2
    ``HttpResponseException`` is part of NuGet package ``Microsoft.AspNetCore.Mvc.WebApiCompatShim`` which provides compatibility in ASP.NET Core MVC with ASP.NET Web API 2. That is, it allows you to go the non-Core way in Core projects. So for Core projects, this is not the "Correct way". – Jack Miller Mar 16 '21 at 06:33
118

Just return the following:

return Unauthorized();
JohnWrensby
  • 1,954
  • 1
  • 14
  • 17
  • 3
    I think the accepted answers the OP's question specifically. My answer answers the question's title "ASP.NET Web API : Correct way to return a 401/unauthorised response" – JohnWrensby Dec 18 '16 at 21:34
  • 3
    Anybody know why there's no overloaded version of this with a message? – Simon_Weaver Jul 09 '18 at 08:07
  • 5
    @Simon_Weaver No idea why, but you could use a `return Content(HttpStatusCode.Unauthorized, "Message");` to do this. – Rikki Sep 17 '18 at 18:03
  • 2
    This should be the correct answer. 1 it is correct. 2) If this changes in a later framework, you don't have to change code. 3) You don't need to provide a reason to a 401. This should be handled by the client and not the server. – Nick Turner Jan 02 '19 at 20:15
  • 1
    Which library is this in? – Nae May 13 '19 at 11:46
  • @Rikki it can be nice to pass some kind of "loggable" message along with the response. That way you can inform the client of the reason they could not authenticate: eg "API key revoked", or "Key does not exist" etc. They can still choose whether to log the message or just ignore it and handle the status code, but at least then you don't leave the developer on the other end wondering why their code won't work – Jon Story Apr 27 '20 at 20:36
26

As an alternative to the other answers, you can also use this code if you want to return an IActionResult within an ASP.NET controller.

ASP.NET

 return Content(HttpStatusCode.Unauthorized, "My error message");

Update: ASP.NET Core

Above code does not work in ASP.NET Core, you can use one of these instead:

 return StatusCode((int)System.Net.HttpStatusCode.Unauthorized, "My error message");
 return StatusCode(Microsoft.AspNetCore.Http.StatusCodes.Status401Unauthorized, "My error message");
 return StatusCode(401, "My error message");

Apparently the reason phrase is pretty optional (Can an HTTP response omit the Reason-Phrase?)

Alex AIT
  • 10,190
  • 3
  • 23
  • 49
  • 2
    This no-longer works in ASP.NET Core, the `ControllerBase` class (used by ASP.NET Core WebAPI) no-longer has a `Content` overload that accepts a HTTP status code. – Dai Sep 07 '18 at 23:54
  • This is wrong. A Content response is a 200 Ok status. The server should send a 401 and the client should handle accordingly. You can't send a 200 as a 401. It doesn't make sense. If the client gets a 401, it's not an Oops, it's a your breaking the law. – Nick Turner Jan 02 '19 at 20:19
  • This code is sending a 401 status code(`HttpStatusCode.Unauthorized`), not 200. `Content(...)` simply a shorthand for returning any given content with a given HTTP status code. If you want to send 200 you can use `Ok(...)` – Alex AIT Jan 02 '19 at 21:39
  • @NickTurner -- that's an argument for the webapi2 Content() method being poorly named not for this being the wrong answer. Since the (status,message) method is renamed in NetCore, I guess the devs agree it was poorly named. – Chris F Carroll Apr 09 '19 at 13:36
10

You get a 500 response code because you're throwing an exception (the HttpException) which indicates some kind of server error, this is the wrong approach.

Just set the response status code .e.g

Response.StatusCode = (int)HttpStatusCode.Unauthorized;
DGibbs
  • 15,052
  • 6
  • 41
  • 75
  • It's a bit odd then that the exception takes the HTTP status code as a parameter, and intellisense docs say that this is the status code sent to the client - I was hoping to avoid mutating the response myself directly as this seems error prone, seeing as its global state – GoatInTheMachine Jul 03 '15 at 12:06
  • 1
    The base Web API controller doesn't expose a `Response` property. – LukeH Jul 03 '15 at 12:07
6

To add to an existing answer in ASP.NET Core >= 1.0 you can

return Unauthorized();

return Unauthorized(object value);

To pass info to the client you can do a call like this:

return Unauthorized(new { Ok = false, Code = Constants.INVALID_CREDENTIALS, ...});

On the client besides the 401 response you will have the passed data too. For example on most clients you can await response.json() to get it.

Gabriel P.
  • 2,552
  • 2
  • 26
  • 19
3

In .Net Core You can use

return new ForbidResult();

instead of

return Unauthorized();

which has the advantage to redirecting to the default unauthorized page (Account/AccessDenied) rather than giving a straight 401

to change the default location modify your startup.cs

services.AddAuthentication(options =>...)
            .AddOpenIdConnect(options =>...)
            .AddCookie(options =>
            {
                options.AccessDeniedPath = "/path/unauthorized";

            })
mattbloke
  • 820
  • 1
  • 8
  • 25
  • The question is about an web API. So this would be an invalid answer if am not wrong? API should not return 'actions', only results. – Niels Lucas Jul 06 '20 at 13:18
  • 2
    403 is Forbidden, not 401 Unauthorized. There's a great explanation about the difference [here](https://stackoverflow.com/a/6937030/3838167) – Mars Nov 24 '20 at 11:47
1

you can use follow code in asp.net core 2.0:

public IActionResult index()
{
     return new ContentResult() { Content = "My error message", StatusCode = (int)HttpStatusCode.Unauthorized };
}
AminRostami
  • 1,585
  • 1
  • 15
  • 33
1

You also follow this code:

var response = new HttpResponseMessage(HttpStatusCode.NotFound)
{
      Content = new StringContent("Users doesn't exist", System.Text.Encoding.UTF8, "text/plain"),
      StatusCode = HttpStatusCode.NotFound
 }
 throw new HttpResponseException(response);
Kamrul Hasan
  • 117
  • 1
  • 12