API Web ASP.NET: méthode correcte pour renvoyer une réponse 401 / non autorisée

99

J'ai un site Webapi MVC qui utilise l'authentification OAuth / jeton pour authentifier les demandes. Tous les contrôleurs concernés ont les bons attributs et l'authentification fonctionne correctement.

Le problème est que toute la demande ne peut pas être autorisée dans la portée d'un attribut - certaines vérifications d'autorisation doivent être effectuées dans un code appelé par les méthodes du contrôleur - quelle est la bonne façon de renvoyer une réponse 401 non autorisée dans ce cas?

J'ai essayé throw new HttpException(401, "Unauthorized access");, mais quand je fais cela, le code d'état de réponse est 500 et j'obtiens également une trace de pile. Même dans notre journalisation DelegatingHandler, nous pouvons voir que la réponse est 500, pas 401.

ChèvreInTheMachine
la source
1
À toute personne qui prendrait cette réponse sur toute la ligne, je suggérerais de réfléchir au moment approprié pour lancer un HttpResponseExceptionpar rapport au moment de renvoyer un Unauthorized(). Utiliser l'exception pour une erreur «attendue» est un peu un anti-modèle, donc s'il y a des cas où vous vous attendez à ce que l'appel fasse cette erreur, le retour Unauthorized()est probablement le bon appel. Économisez HttpResponseExceptionpour le vraiment inattendu.
Rikki le
Voir github.com/aspnet/Mvc/issues/5507 pour une discussion.
Rikki
@Rikki, 401 n'est pas une erreur "attendue". - C'est une circonstance exceptionnelle qui devrait vous amener à abandonner votre workflow (sauf peut-être pour la journalisation, ce que vous devriez déjà faire pour toute exception ...) - Quoi qu'il en soit, si vous souhaitez renvoyer un résultat typé fort de votre contrôleur ( par exemple pour faciliter les tests unitaires), une exception est clairement la meilleure voie.
BrainSlugs83

Réponses:

145

Vous devriez lancer un à HttpResponseExceptionpartir de votre méthode API, pas HttpException:

throw new HttpResponseException(HttpStatusCode.Unauthorized);

Ou, si vous souhaitez fournir un message personnalisé:

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

Renvoyez simplement ce qui suit:

return Unauthorized();
JohnWrensby
la source
2
Je pense que l'acceptation répond spécifiquement à la question du PO. Ma réponse répond au titre de la question "API Web ASP.NET: moyen correct de renvoyer une réponse 401 / non autorisée"
JohnWrensby
3
Quelqu'un sait pourquoi il n'y a pas de version surchargée de cela avec un message?
Simon_Weaver
5
@Simon_Weaver Je ne sais pas pourquoi, mais vous pouvez utiliser un return Content<string>(HttpStatusCode.Unauthorized, "Message");pour le faire.
Rikki le
2
Cela devrait être la bonne réponse. 1 c'est correct. 2) Si cela change dans un cadre ultérieur, vous n'avez pas à changer de code. 3) Vous n'avez pas besoin de fournir une raison à un 401. Cela devrait être géré par le client et non par le serveur.
Nick Turner
1
Dans quelle bibliothèque se trouve-t-il?
Nae
19

Comme alternative aux autres réponses, vous pouvez également utiliser ce code si vous souhaitez renvoyer un IActionResultdans un contrôleur ASP.NET.

ASP.NET

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

Mise à jour: ASP.NET Core

Le code ci-dessus ne fonctionne pas dans ASP.NET Core, vous pouvez utiliser l'un de ceux-ci à la place:

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

Apparemment, la phrase de raison est assez facultative ( une réponse HTTP peut-elle omettre la phrase de raison? )

Alex AIT
la source
1
Cela ne fonctionne plus dans ASP.NET Core, la ControllerBaseclasse (utilisée par ASP.NET Core WebAPI) n'a plus de Contentsurcharge qui accepte un code d'état HTTP.
Dai
C'est faux. Une réponse Content est un statut 200 Ok. Le serveur doit envoyer un 401 et le client doit gérer en conséquence. Vous ne pouvez pas envoyer un 200 comme un 401. Cela n'a aucun sens. Si le client obtient un 401, ce n'est pas un Oups, c'est votre infraction à la loi.
Nick Turner
Ce code envoie un code d'état 401 ( HttpStatusCode.Unauthorized), pas 200. Content(...)simplement un raccourci pour renvoyer un contenu donné avec un code d'état HTTP donné. Si vous voulez envoyer 200, vous pouvez utiliserOk(...)
Alex AIT
@NickTurner - c'est un argument pour que la méthode webapi2 Content () soit mal nommée, pas pour que ce soit la mauvaise réponse. Puisque la méthode (status, message) est renommée dans NetCore, je suppose que les développeurs conviennent qu'elle a été mal nommée.
Chris F Carroll
9

Vous obtenez un code de réponse 500 parce que vous lancez une exception (le HttpException) qui indique une sorte d'erreur de serveur, c'est la mauvaise approche.

Définissez simplement le code d'état de la réponse .eg

Response.StatusCode = (int)HttpStatusCode.Unauthorized;
DGibbs
la source
C'est un peu étrange alors que l'exception prenne le code d'état HTTP comme paramètre, et les documents intellisense disent que c'est le code d'état envoyé au client - j'espérais éviter de muter la réponse moi-même directement car cela semble sujet aux erreurs, vu que son état global
GoatInTheMachine
1
Le contrôleur de l'API Web de base n'expose pas de Responsepropriété.
LukeH
3

Pour ajouter à une réponse existante dans ASP.NET Core> = 1.0, vous pouvez

return Unauthorized();

return Unauthorized(object value);

Pour transmettre des informations au client, vous pouvez faire un appel comme ceci:

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

Sur le client, en plus de la réponse 401, vous aurez également les données transmises. Par exemple, sur la plupart des clients, vous pouvez l' await response.json()obtenir.

Gabriel P.
la source
3

Dans .Net Core, vous pouvez utiliser

return new ForbidResult();

au lieu de

return Unauthorized();

qui a l'avantage de rediriger vers la page non autorisée par défaut (Account / AccessDenied) plutôt que de donner un simple 401

pour changer l'emplacement par défaut, modifiez votre startup.cs

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

            })
mattbloke
la source
La question concerne une API Web. Donc, ce serait une réponse invalide si je ne me trompe pas? L'API ne doit pas renvoyer des "actions", uniquement des résultats.
Niels Lucas le
1

vous pouvez utiliser le code suivant dans asp.net core 2.0:

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

Vous suivez également ce 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
la source
Vous n'avez pas besoin de définir à nouveau le StatusCode si vous le transmettez au constructeur - en utilisant l'un ou l'autre, c'est bien
Jon Story