ASP.NET Web API: maneira correta de retornar uma resposta 401 / não autorizada

98

Eu tenho um site MVC webapi que usa autenticação OAuth / token para autenticar solicitações. Todos os controladores relevantes têm os atributos corretos e a autenticação está funcionando bem.

O problema é que nem todas as solicitações podem ser autorizadas no escopo de um atributo - algumas verificações de autorização devem ser realizadas no código que é chamado pelos métodos do controlador - qual é a maneira correta de retornar uma resposta 401 não autorizada neste caso?

Eu tentei throw new HttpException(401, "Unauthorized access");, mas quando faço isso, o código de status de resposta é 500 e também consigo um rastreamento de pilha. Mesmo em nosso registro DelegatingHandler, podemos ver que a resposta é 500, não 401.

GoatInTheMachine
fonte
1
Para qualquer pessoa que esteja pegando essa resposta no final da linha, sugiro pensar sobre o momento apropriado para lançar um HttpResponseExceptionversus quando retornar um Unauthorized(). Usar a exceção para um erro 'esperado' é um pouco antipadrão, portanto, se houver casos em que você espera que a chamada cometa esse erro, retornar Unauthorized()é provavelmente a chamada certa. Economize HttpResponseExceptionpara o verdadeiramente inesperado.
Rikki
Consulte github.com/aspnet/Mvc/issues/5507 para uma discussão.
Rikki
@Rikki, 401 não é um erro "esperado". - É uma circunstância excepcional que deve fazer com que você aborte seu fluxo de trabalho (exceto talvez para registro, o que você já deveria estar fazendo para qualquer exceção ...) - De qualquer forma, se você quiser retornar um resultado forte digitado de seu controlador ( por exemplo, para facilitar o teste de unidade), uma exceção é claramente o melhor caminho.
BrainSlugs83,

Respostas:

145

Você deve lançar um HttpResponseExceptionde seu método de API, não HttpException:

throw new HttpResponseException(HttpStatusCode.Unauthorized);

Ou, se você deseja fornecer uma mensagem personalizada:

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

Basta devolver o seguinte:

return Unauthorized();
JohnWrensby
fonte
2
Acho que o aceito responde especificamente à pergunta do OP. Minha resposta responde ao título da pergunta "ASP.NET Web API: maneira correta de retornar uma resposta 401 / não autorizada"
JohnWrensby
3
Alguém sabe por que não existe uma versão sobrecarregada disso com uma mensagem?
Simon_Weaver
5
@Simon_Weaver Não tenho ideia do porquê, mas você poderia usar um return Content<string>(HttpStatusCode.Unauthorized, "Message");para fazer isso.
Rikki
2
Esta deve ser a resposta correta. 1 está correto. 2) Se isso mudar em uma estrutura posterior, você não precisará alterar o código. 3) Você não precisa fornecer um motivo para um 401. Isso deve ser tratado pelo cliente e não pelo servidor.
Nick Turner
1
Em qual biblioteca isso está?
Nae
19

Como alternativa às outras respostas, você também pode usar esse código se quiser retornar um IActionResultem um controlador ASP.NET.

ASP.NET

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

Atualização: ASP.NET Core

O código acima não funciona no ASP.NET Core, você pode usar um destes em seu lugar:

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

Aparentemente, a frase de razão é bastante opcional ( uma resposta HTTP pode omitir a frase de razão? )

Alex AIT
fonte
1
Isso não funciona mais no ASP.NET Core, a ControllerBaseclasse (usada pelo ASP.NET Core WebAPI) não tem mais uma Contentsobrecarga que aceita um código de status HTTP.
Dia
Isto está errado. Uma resposta de conteúdo é um status 200 Ok. O servidor deve enviar um 401 e o cliente deve lidar com isso de acordo. Você não pode enviar um 200 como um 401. Não faz sentido. Se o cliente receber um 401, não é um Ops, é uma violação da lei.
Nick Turner
Este código está enviando um código de status 401 ( HttpStatusCode.Unauthorized), não 200. Content(...)simplesmente uma abreviação para retornar qualquer conteúdo com um determinado código de status HTTP. Se você quiser enviar 200, você pode usarOk(...)
Alex AIT
@NickTurner - esse é um argumento para o método webapi2 Content () ser mal nomeado, não por ser a resposta errada. Como o método (status, mensagem) foi renomeado no NetCore, acho que os desenvolvedores concordam que foi mal nomeado.
Chris F Carroll de
9

Você obtém um código de resposta 500 porque está lançando uma exceção (o HttpException) que indica algum tipo de erro do servidor, essa é a abordagem errada.

Basta definir o código de status de resposta.

Response.StatusCode = (int)HttpStatusCode.Unauthorized;
DGibbs
fonte
É um pouco estranho então que a exceção tome o código de status HTTP como um parâmetro, e os documentos da Intellisense dizem que este é o código de status enviado ao cliente - eu esperava evitar a mutação da resposta eu mesmo diretamente, pois isso parece sujeito a erros, visto que seu estado global
GoatInTheMachine
1
O controlador de API da Web base não expõe uma Responsepropriedade.
LukeH
3

Para adicionar a uma resposta existente no ASP.NET Core> = 1.0, você pode

return Unauthorized();

return Unauthorized(object value);

Para passar informações ao cliente, você pode fazer uma chamada como esta:

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

No cliente além da resposta 401 você também terá os dados passados. Por exemplo, na maioria dos clientes, você pode await response.json()obtê-lo.

Gabriel P.
fonte
3

Em .Net Core você pode usar

return new ForbidResult();

ao invés de

return Unauthorized();

que tem a vantagem de redirecionar para a página não autorizada padrão (Conta / Acesso Negado) em vez de fornecer um 401 direto

para alterar o local padrão, modifique seu startup.cs

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

            })
mattbloke
fonte
A questão é sobre uma API da web. Portanto, esta seria uma resposta inválida se não estou errado? API não deve retornar 'ações', apenas resultados.
Niels Lucas
1

você pode usar o código a seguir no asp.net core 2.0:

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

Você também segue 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
fonte
Você não precisa definir o StatusCode novamente se passá-lo para o construtor - usar qualquer um deles está certo
Jon Story