ASP.NET Web API: forma correcta de devolver una respuesta 401 / no autorizada

98

Tengo un sitio web MVC que usa autenticación OAuth / token para autenticar solicitudes. Todos los controladores relevantes tienen los atributos correctos y la autenticación funciona correctamente.

El problema es que no todas las solicitudes se pueden autorizar en el alcance de un atributo; algunas verificaciones de autorización deben realizarse en el código al que llaman los métodos del controlador; ¿cuál es la forma correcta de devolver una respuesta 401 no autorizada en este caso?

Lo he intentado throw new HttpException(401, "Unauthorized access");, pero cuando hago esto, el código de estado de respuesta es 500 y también obtengo un seguimiento de la pila. Incluso en nuestro DelegatingHandler de registro podemos ver que la respuesta es 500, no 401.

CabraEnLaMáquina
fuente
1
A cualquiera que obtenga esta respuesta en el futuro, le sugiero que piense en el momento adecuado para lanzar un HttpResponseExceptionversus cuándo devolver un Unauthorized(). Usar la excepción para un error 'esperado' es un poco antipatrón, por lo que si hay casos en los que espera que la llamada cometa este error, regresar Unauthorized()es probablemente la llamada correcta. Ahorre HttpResponseExceptionpara lo verdaderamente inesperado.
Rikki
Consulte github.com/aspnet/Mvc/issues/5507 para una discusión.
Rikki
@Rikki, 401 no es un error "esperado". - Es una circunstancia excepcional que debería hacer que cancele su flujo de trabajo (excepto tal vez para el registro, que ya debería estar haciendo para cualquier excepción ...) - De todos modos, si desea devolver un resultado de tipo fuerte desde su controlador ( por ejemplo, para facilitar las pruebas unitarias), una excepción es claramente la mejor ruta.
BrainSlugs83

Respuestas:

145

Debería lanzar un HttpResponseExceptiondesde su método API, no HttpException:

throw new HttpResponseException(HttpStatusCode.Unauthorized);

O, si desea proporcionar un mensaje personalizado:

var msg = new HttpResponseMessage(HttpStatusCode.Unauthorized) { ReasonPhrase = "Oops!!!" };
throw new HttpResponseException(msg);
LukeH
fuente
95

Simplemente devuelva lo siguiente:

return Unauthorized();
JohnWrensby
fuente
2
Creo que el aceptado responde específicamente a la pregunta del OP. Mi respuesta responde al título de la pregunta "ASP.NET Web API: forma correcta de devolver un 401 / respuesta no autorizada"
JohnWrensby
3
¿Alguien sabe por qué no hay una versión sobrecargada de esto con un mensaje?
Simon_Weaver
5
@Simon_Weaver No tengo idea de por qué, pero podrías usar un return Content<string>(HttpStatusCode.Unauthorized, "Message");para hacer esto.
Rikki
2
Esta debería ser la respuesta correcta. 1 es correcto. 2) Si esto cambia en un marco posterior, no es necesario que cambie el código. 3) No es necesario dar una razón a un 401. Esto debe ser manejado por el cliente y no por el servidor.
Nick Turner
1
¿En qué biblioteca está esto?
Nae
19

Como alternativa a las otras respuestas, también puede usar este código si desea devolver un IActionResultdentro de un controlador ASP.NET.

ASP.NET

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

Actualización: ASP.NET Core

El código anterior no funciona en ASP.NET Core, puede usar uno de estos en su lugar:

 return StatusCode((int)System.Net.HttpStatusCode.Unauthorized, "My error message");
 return StatusCode(401, "My error message");

Aparentemente, la frase de motivo es bastante opcional ( ¿Puede una respuesta HTTP omitir la frase de motivo? )

Alex AIT
fuente
1
Esto ya no funciona en ASP.NET Core, la ControllerBaseclase (utilizada por ASP.NET Core WebAPI) ya no tiene una Contentsobrecarga que acepta un código de estado HTTP.
Dai
Esto está mal. Una respuesta de contenido es un estado de 200 ok. El servidor debe enviar un 401 y el cliente debe manejarlo en consecuencia. No puedes enviar un 200 como un 401. No tiene sentido. Si el cliente obtiene un 401, no es un Vaya, es una violación de la ley.
Nick Turner
Este código envía un código de estado 401 ( HttpStatusCode.Unauthorized), no 200. Content(...)simplemente una forma abreviada de devolver cualquier contenido dado con un código de estado HTTP dado. Si desea enviar 200, puede usarOk(...)
Alex AIT
@NickTurner: ese es un argumento para que el método webapi2 Content () tenga un nombre deficiente, no porque esta sea la respuesta incorrecta. Dado que el método (estado, mensaje) ha cambiado de nombre en NetCore, supongo que los desarrolladores están de acuerdo en que se le dio un nombre deficiente.
Chris F Carroll
9

Obtiene un código de respuesta 500 porque está lanzando una excepción (the HttpException) que indica algún tipo de error del servidor, este es el enfoque incorrecto.

Simplemente configure el código de estado de respuesta .eg

Response.StatusCode = (int)HttpStatusCode.Unauthorized;
DGibbs
fuente
Es un poco extraño entonces que la excepción tome el código de estado HTTP como parámetro, y los documentos de intellisense dicen que este es el código de estado enviado al cliente; esperaba evitar mutar la respuesta yo mismo directamente, ya que parece propenso a errores, ya que su estado global
GoatInTheMachine
1
El controlador de API web base no expone una Responsepropiedad.
LukeH
3

Para agregar a una respuesta existente en ASP.NET Core> = 1.0, puede

return Unauthorized();

return Unauthorized(object value);

Para pasar información al cliente, puede hacer una llamada como esta:

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

En el cliente, además de la respuesta 401, también tendrá los datos pasados. Por ejemplo, en la mayoría de los clientes puede await response.json()conseguirlo.

Gabriel P.
fuente
3

En .Net Core puede usar

return new ForbidResult();

en vez de

return Unauthorized();

que tiene la ventaja de redirigir a la página no autorizada predeterminada (Account / AccessDenied) en lugar de dar un 401 directo

para cambiar la ubicación predeterminada modifique su startup.cs

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

            })
mattbloke
fuente
La pregunta es sobre una API web. ¿Entonces esta sería una respuesta inválida si no me equivoco? La API no debe devolver "acciones", solo resultados.
Niels Lucas
1

puede usar el código de seguimiento en asp.net core 2.0:

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

También sigues este código:

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
fuente
No es necesario volver a configurar el StatusCode si se lo pasa al constructor; usar cualquiera de los dos está bien
Jon Story