Search code examples
c#asp.net-web-apiasp.net-mvc-5asp.net-identityowin

Unauthorised webapi call returning login page rather than 401


How do I configure my mvc/webapi project so that a webapi method called from a razor view doesn't return the loginpage when its unauthorised?

Its a MVC5 application which also has WebApi controllers for calls via javascript.

The two methods below

[Route("api/home/LatestProblems")]      
[HttpGet()]
public List<vmLatestProblems> LatestProblems()
{
    // Something here
}

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

are called via the following angular code:

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);
        });  
    }]
);

So I'm not logged in and the first method successfully returns data. the second method returns (in the success function) data which contains the equivalent of a login page. i.e. what you would get in mvc if you requested a controller action which was stamped with [Authorize] and you weren't logged in.

I want it to return a 401 unauthorized, so that i can display different data for users based on if they are logged in or not. Ideally if the user is logged in i want to be able to access the Controller's User property so i can return data specific to that Member.

UPDATE: Since none of the suggestions below seem to work anymore (changes to Identity or WebAPI) ive created a raw example on github which should illustrate the problem.


Solution

  • There are two AuthorizeAttribute implementations and you need to make sure you are referencing the correct one for Web API's. There is System.Web.Http.AuthorizeAttribute which is used for Web API's, and System.Web.Mvc.AuthorizeAttribute which is used for controllers with views. Http.AuthorizeAttribute will return a 401 error if authorization fails and Mvc.AuthorizeAttribute will redirect to the login page.

    Updated 11/26/2013

    So it appears things have drastically changed with MVC 5 as Brock Allen pointed out in his article. I guess the OWIN pipeline takes over and introduces some new behavior. Now when the user is not authorized a status of 200 is returned with the following information in the HTTP header.

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

    You could change your logic on the client side to check this information in the header to determine how to handle this, instead of looking for a 401 status on the error branch.

    I tried to override this behavior in a custom AuthorizeAttribute by setting the status in the response in the OnAuthorization and HandleUnauthorizedRequest methods.

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

    But this did not work. The new pipeline must grab this response later and modify it to the same response I was getting before. Throwing an HttpException did not work either as it is just changed into a 500 error status.

    I tested Brock Allen's solution and it did work when I was using a jQuery ajax call. If it is not working for you my guess is that it is because you are using angular. Run your test with Fiddler and see if the following is in your header.

    X-Requested-With: XMLHttpRequest
    

    If it is not then that is the problem. I am not familiar with angular but if it lets you insert your own header values then add this to your ajax requests and it will probably start working.