Appel Webapi non autorisé renvoyant la page de connexion au lieu de 401

Comment configurer mon projet mvc / webapi pour qu’une méthode webapi appelée à partir d’une vue de razor ne renvoie pas la page de connexion lorsqu’elle n’est pas autorisée?

C’est une application MVC5 qui dispose également de contrôleurs WebApi pour les appels via javascript.

Les deux méthodes ci-dessous

[Route("api/home/LatestProblems")] [HttpGet()] public List LatestProblems() { // Something here } [Route("api/home/myLatestProblems")] [HttpGet()] [Authorize(Roles = "Member")] public List mylatestproblems() { // Something there } 

sont appelés via le code angular suivant:

 angular.module('appWorship').controller('latest', ['$scope', '$http', function ($scope,$http) { var urlBase = baseurl + '/api/home/LatestProblems'; $http.get(urlBase).success(function (data) { $scope.data = data; }).error(function (data) { console.log(data); }); $http.get(baseurl + '/api/home/mylatestproblems') .success(function (data) { $scope.data2 = data; }).error(function (data) { console.log(data); }); }] ); 

Je ne suis donc pas connecté et la première méthode renvoie avec succès des données. la seconde méthode renvoie (dans la fonction succès) des données contenant l’équivalent d’une page de connexion. c’est-à-dire ce que vous obtiendriez dans mvc si vous demandiez une action de contrôleur qui était marquée avec [Authorize] et que vous n’étiez pas connecté.

Je veux qu’il retourne un 401 non autorisé, afin que je puisse afficher des données différentes pour les utilisateurs en fonction de leur connexion ou non. Idéalement, si l’utilisateur est connecté, je souhaite pouvoir accéder à la propriété Utilisateur du contrôleur afin de pouvoir renvoyer des données spécifiques à ce Membre.

MISE À JOUR: Étant donné qu’aucune des suggestions ci-dessous ne semble plus fonctionner (modifications apscopes à Identity ou WebAPI), ive a créé un exemple brut sur github qui devrait illustrer le problème.

    Il existe deux implémentations AuthorizeAtsortingbute et vous devez vous assurer que vous faites référence au correct pour les API Web. System.Web.Http.AuthorizeAtsortingbute est utilisé pour les API Web et System.Web.Mvc.AuthorizeAtsortingbute qui est utilisé pour les contrôleurs avec vues. Http.AuthorizeAtsortingbute renverra une erreur 401 si l’autorisation échoue et que Mvc.AuthorizeAtsortingbute sera redirigé vers la page de connexion.

    Mise à jour 26/11/2013

    Il semble donc que les choses ont radicalement changé avec MVC 5, comme l’a souligné Brock Allen dans son article . Je suppose que le pipeline OWIN prend le relais et introduit un nouveau comportement. Maintenant, lorsque l’utilisateur n’est pas autorisé, un statut de 200 est renvoyé avec les informations suivantes dans l’en-tête HTTP.

     X-Responded-JSON: {"status":401,"headers":{"location":"http:\/\/localhost:59540\/Account\/Login?ReturnUrl=%2Fapi%2FTestBasic"}} 

    Vous pouvez modifier votre logique du côté client pour vérifier ces informations dans l’en-tête afin de déterminer comment gérer cela, au lieu de rechercher un statut 401 sur la twig d’erreur.

    J’ai essayé de remplacer ce comportement dans un AuthorizeAtsortingbute personnalisé en définissant le statut dans la réponse dans les méthodes OnAuthorization et HandleUnauthorizedRequest .

     actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized); 

    Mais cela n’a pas fonctionné. Le nouveau pipeline doit récupérer cette réponse ultérieurement et la modifier pour obtenir la même réponse que précédemment. Lancer une exception HttpException ne fonctionnait pas non plus car il est simplement changé en un état d’erreur 500.

    J’ai testé la solution de Brock Allen et celle-ci a fonctionné lorsque j’utilisais un appel jQuery ajax. Si cela ne fonctionne pas pour vous, je suppose que c’est parce que vous utilisez angular. Exécutez votre test avec Fiddler et voyez si ce qui suit est dans votre en-tête.

     X-Requested-With: XMLHttpRequest 

    Si ce n’est pas le cas, c’est le problème. Je ne suis pas familier avec angular mais si cela vous permet d’insérer vos propres valeurs d’en-tête alors ajoutez ceci à vos demandes d’ajax et il commencera probablement à fonctionner.

    Brock Allen a un bon blog sur la façon de retourner 401 pour les appels ajax lors de l’utilisation de l’authentification par cookie et OWIN. http://brockallen.com/2013/10/27/using-cookie-authentication-middleware-with-web-api-and-401-response-codes/

    Placez cette méthode dans ConfigureAuth dans le fichier Startup.Auth.cs:

     app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathSsortingng("/Account/Login"), Provider = new CookieAuthenticationProvider { OnApplyRedirect = ctx => { if (!IsAjaxRequest(ctx.Request)) { ctx.Response.Redirect(ctx.RedirectUri); } } } }); private static bool IsAjaxRequest(IOwinRequest request) { IReadableSsortingngCollection query = request.Query; if ((query != null) && (query["X-Requested-With"] == "XMLHttpRequest")) { return true; } IHeaderDictionary headers = request.Headers; return ((headers != null) && (headers["X-Requested-With"] == "XMLHttpRequest")); } 

    Si vous ajoutez asp.net WebApi dans le site Web asp.net MVC, vous souhaiterez probablement répondre sans autorisation à certaines demandes. Mais alors, l’infrastructure ASP.NET entre en jeu et lorsque vous essayez de définir le code d’état de la réponse sur HttpStatusCode.Unauthorized, vous obtiendrez une redirection 302 vers la page de connexion.

    Si vous utilisez l’identité asp.net et l’authentification basée sur owin, voici un code qui peut vous aider à résoudre ce problème:

     public void ConfigureAuth(IAppBuilder app) { app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathSsortingng("/Account/Login"), Provider = new CookieAuthenticationProvider() { OnApplyRedirect = ctx => { if (!IsApiRequest(ctx.Request)) { ctx.Response.Redirect(ctx.RedirectUri); } } } }); app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); } private static bool IsApiRequest(IOwinRequest request) { ssortingng apiPath = VirtualPathUtility.ToAbsolute("~/api/"); return request.Uri.LocalPath.StartsWith(apiPath); } 

    J’ai eu la même situation lorsque OWIN redirige toujours la réponse 401 vers la page de connexion à partir de WebApi. Par conséquent, la solution pour vérifier si la requête est une requête ajax n’est pas vraiment sortingée pour notre cas.

    J’ai opté pour une autre approche: injecter une nouvelle réponse d’en-tête: Suppress-Redirect si les réponses proviennent de webApi. L’implémentation est sur le gestionnaire:

     public class SuppressRedirectHandler : DelegatingHandler { ///  protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { return base.SendAsync(request, cancellationToken).ContinueWith(task => { var response = task.Result; response.Headers.Add("Suppress-Redirect", "True"); return response; }, cancellationToken); } } 

    Et enregistrez ce gestionnaire au niveau global de WebApi:

     config.MessageHandlers.Add(new SuppressRedirectHandler()); 

    Ainsi, au démarrage d’OWIN, vous pouvez vérifier si l’en-tête de réponse a Suppress-Redirect :

     public void Configuration(IAppBuilder app) { app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationMode = AuthenticationMode.Active, AuthenticationType = DefaultApplicationTypes.ApplicationCookie, ExpireTimeSpan = TimeSpan.FromMinutes(48), LoginPath = new PathSsortingng("/NewAccount/LogOn"), Provider = new CookieAuthenticationProvider() { OnApplyRedirect = ctx => { var response = ctx.Response; if (!IsApiResponse(ctx.Response)) { response.Redirect(ctx.RedirectUri); } } } }); } private static bool IsApiResponse(IOwinResponse response) { var responseHeader = response.Headers; if (responseHeader == null) return false; if (!responseHeader.ContainsKey("Suppress-Redirect")) return false; if (!bool.TryParse(responseHeader["Suppress-Redirect"], out bool suppressRedirect)) return false; return suppressRedirect; } 

    Dans les versions précédentes d’ASP.NET, vous deviez faire tout un tas de choses pour que cela fonctionne.

    Les bonnes nouvelles sont, puisque vous utilisez ASP.NET 4.5. Vous pouvez désactiver la redirection de l’authentification par formulaires à l’aide de la nouvelle propriété HttpResponse.SuppressFormsAuthenticationRedirect .

    Dans Global.asax :

     protected void Application_EndRequest(Object sender, EventArgs e) { HttpApplication context = (HttpApplication)sender; context.Response.SuppressFormsAuthenticationRedirect = true; } 

    EDIT : Vous voudrez peut-être aussi jeter un coup d’œil à cet article de Sergey Zwezdin qui propose une manière plus raffinée de réaliser ce que vous essayez de faire.

    Extraits de code pertinents et narration de l’auteur collés ci-dessous. Auteur original du code et de la narration – Sergey Zwezdin .

    Premièrement – déterminons si la requête HTTP actuelle est une requête AJAX. Si oui, nous devrions désactiver le remplacement de HTTP 401 par HTTP 302:

     public class ApplicationAuthorizeAtsortingbute : AuthorizeAtsortingbute { protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { var httpContext = filterContext.HttpContext; var request = httpContext.Request; var response = httpContext.Response; if (request.IsAjaxRequest()) response.SuppressFormsAuthenticationRedirect = true; base.HandleUnauthorizedRequest(filterContext); } } 

    Deuxièmement – ajoutons une condition :: si l’utilisateur est authentifié, alors nous enverrons HTTP 403; et HTTP 401 sinon.

     public class ApplicationAuthorizeAtsortingbute : AuthorizeAtsortingbute { protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { var httpContext = filterContext.HttpContext; var request = httpContext.Request; var response = httpContext.Response; var user = httpContext.User; if (request.IsAjaxRequest()) { if (user.Identity.IsAuthenticated == false) response.StatusCode = (int)HttpStatusCode.Unauthorized; else response.StatusCode = (int)HttpStatusCode.Forbidden; response.SuppressFormsAuthenticationRedirect = true; response.End(); } base.HandleUnauthorizedRequest(filterContext); } } 

    Bien joué. Maintenant, nous devrions remplacer tous les usages de la norme AuthorizeAtsortingbute par ce nouveau filtre. Cela peut ne pas être applicable pour les mecs qui sont esthètes du code. Mais je ne connais pas d’autre moyen. Si vous avez, passons aux commentaires, s’il vous plaît.

    Le dernier, ce que nous devrions faire – append la gestion HTTP 401/403 côté client. Nous pouvons utiliser ajaxError à jQuery pour éviter la duplication de code:

     $(document).ajaxError(function (e, xhr) { if (xhr.status == 401) window.location = "/Account/Login"; else if (xhr.status == 403) alert("You have no enough permissions to request this resource."); }); 

    Le résultat –

    • Si l’utilisateur n’est pas authentifié, il sera redirigé vers une page de connexion après un appel AJAX.
    • Si l’utilisateur est authentifié, mais n’a pas assez d’permissions, il verra un message erorr convivial.
    • Si l’utilisateur est authentifié et dispose de suffisamment d’permissions, il n’y a pas d’erreurs et la demande HTTP se poursuit normalement.

    En utilisant moi-même l’intégration d’Azure Active Directory, l’approche utilisant le middleware CookieAuthentication ne fonctionnait pas pour moi. J’ai dû faire ce qui suit:

     app.UseOpenIdConnectAuthentication( new OpenIdConnectAuthenticationOptions { ... Notifications = new OpenIdConnectAuthenticationNotifications { ... RedirectToIdentityProvider = async context => { if (!context.Request.Accept.Contains("html")) { context.HandleResponse(); } }, ... } }); 

    Si la requête provient du navigateur lui-même (et non d’un appel AJAX, par exemple), l’en-tête Accept contiendra la chaîne html . Ce n’est que lorsque le client accepte HTML que je considérerai une redirection comme quelque chose d’utile.

    Mon application client peut gérer le 401 en informant l’utilisateur que l’application n’a plus access et doit recharger pour se connecter à nouveau.

    Si vous exécutez votre Web API depuis votre projet MVC , vous devez créer un AuthorizeAtsortingbute personnalisé à appliquer à vos méthodes API . Dans le override IsAuthorized vous devez récupérer le HttpContext actuel afin d’empêcher la redirection, comme ceci:

      protected override bool IsAuthorized(HttpActionContext actionContext) { if (ssortingng.IsNullOrWhiteSpace(Thread.CurrentPrincipal.Identity.Name)) { var response = HttpContext.Current.Response; response.SuppressFormsAuthenticationRedirect = true; response.StatusCode = (int)System.Net.HttpStatusCode.Forbidden; response.End(); } return base.IsAuthorized(actionContext); } 

    J’avais aussi une application MVC5 (System.Web) avec WebApi (en utilisant OWIN) et je voulais juste empêcher que 401 réponses de WebApi soient remplacées par 302 réponses.

    Ce qui a fonctionné pour moi était de créer une version personnalisée de WebApi AuthorizeAtsortingbute comme ceci:

     public class MyAuthorizeAtsortingbute : System.Web.Http.AuthorizeAtsortingbute { protected override void HandleUnauthorizedRequest(HttpActionContext actionContext) { base.HandleUnauthorizedRequest(actionContext); HttpContext.Current.Response.SuppressFormsAuthenticationRedirect = true; } } 

    Et pour l’utiliser à la place du WebApi AuthorizeAtsortingbute standard. J’ai utilisé le standard MVC AuthorizeAtsortingbute pour garder le comportement MVC inchangé.

    Si vous voulez intercepter Content-Type == application / json, vous pouvez utiliser ce code:

     private static bool IsAjaxRequest(IOwinRequest request) { IReadableSsortingngCollection queryXML = request.Query; if ((queryXML != null) && (queryXML["X-Requested-With"] == "XMLHttpRequest")) { return true; } IReadableSsortingngCollection queryJSON = request.Query; if ((queryJSON != null) && (queryJSON["Content-Type"] == "application/json")) { return true; } IHeaderDictionary headersXML = request.Headers; var isAjax = ((headersXML != null) && (headersXML["X-Requested-With"] == "XMLHttpRequest")); IHeaderDictionary headers = request.Headers; var isJson = ((headers != null) && (headers["Content-Type"] == "application/json")); return isAjax || isJson; } 

    Cordialement!!

    J’avais du mal à obtenir le code d’état et une réponse textuelle fonctionnant avec les méthodes OnAuthorization / HandleUnauthorizedRequest. Cela s’est avéré être la meilleure solution pour moi:

      actionContext.Response = new HttpResponseMessage() { StatusCode = HttpStatusCode.Forbidden, Content = new SsortingngContent(unauthorizedMessage) }; 

    Après de nombreuses tentatives pour éviter les redirections vers la page de connexion, je me suis rendu compte que cela était tout à fait approprié pour l’atsortingbut Authorize. Il dit aller et obtenir une autorisation. Au lieu des appels Api qui ne sont pas autorisés, je voulais juste ne pas révéler d’informations à des pirates informatiques. Cet objective était plus facile à atteindre directement en ajoutant un nouvel atsortingbut dérivé de Authorize, qui masque plutôt le contenu en tant qu’erreur 404:

     public class HideFromAnonymousUsersAtsortingbute : AuthorizeAtsortingbute { protected override void HandleUnauthorizedRequest(HttpActionContext actionContext) { actionContext.Response = ActionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, "Access Ressortingcted"); } } 

    Dans MVC 5 avec Dot Net Framework 4.5.2, nous obtenons “application / json, plaint text ..” sous l’en-tête “Accept”.

     isJson = headers["Content-Type"] == "application/json" || headers["Accept"].IndexOf("application/json", System.SsortingngComparison.CurrentCultureIgnoreCase) >= 0; 

    Installez simplement après le paquet NeGet

    Paquet d’installation Microsoft.AspNet.WebApi.Owin

    Écrivez le code suivant dans le fichier WebApiConfig.

     public static class WebApiConfig { public static void Register(HttpConfiguration config) { //Web API configuration and services //Configure Web API to use only bearer token authentication. config.SuppressDefaultHostAuthentication(); config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType)); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html")); config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data")); } }