22

I'm using angularjs http service in my app, and I noticed this in the website:

If the AJAX call succeeds (the server sends back an HTTP code between 200 and 209), the function passed to the success() function is executed. If the AJAX call fails (all other codes except for redirects), the function passed to the error() method is executed.

Obviously, the redirects status code isn't part of success neither error callbacks. So how are we going to handle redirects?

This is what my script does, get data from server using http get. If session expires, server returns 302 status code. I should catch that status code then redirect the page to login.

app.controller('GraphController', function($scope, $http, localStorageService, $interval) {
        $scope.GraphData = [{"key":"in","values":[]},{"key":"out","values":[]}]; 

        $scope.pollStats = function() {
            $http.get('/statistics/getStat').success(function(lastData, status, headers) {              
                if (status==302) {
                    //this doesn't work
                    //window.location=headers('Location');
                    //this doesn't work either
                    window.location.replace(headers('Location'));
                }else if (status==200) {
                    ...processData
                }
            });
        };
});

Obviously my script won't work because success callback can't handle redirects. So how are we supposed to handle that?

bad_coder
  • 11,289
  • 20
  • 44
  • 72
user3714598
  • 1,733
  • 5
  • 28
  • 45
  • I know this is an old question but I have to ask. Are you trying implement OAuth2 redirects? If so, this is not the correct way to do this and it will not be secure. Part of the security of OAuth2 is that the login is provided by the authorization server which has control over the redirect. You should just get a 401 when your token expires and then you know you need to go back to autheticate. Where is the server trying to redirect you to? – Ed Greaves Mar 01 '17 at 22:02

3 Answers3

32

Please note that this is specific to my project.

Goal: Catch 302 status code and redirect the page (to login in my case).

Result: In firebug, I could see that the response code is 302 but then I found out that angularjs returns 200 by simply printing the value of status (response.status). So at first you'd thought that you're hopeless. But in my case, what I did was get the data (response.data), look for the string that could be only found in my login page. Then that's it. problem solved :D

The idea was taken here.

Here's the code

app.factory('redirectInterceptor', function($q,$location,$window){
    return  {
        'response':function(response){
        if (typeof response.data === 'string' && response.data.indexOf("My Login Page")>-1) {
            console.log("LOGIN!!");
            console.log(response.data);
            $window.location.href = "/login.html";
            return $q.reject(response);
        }else{
            return response;
        }
        }
    }

    });

    app.config(['$httpProvider',function($httpProvider) {
        $httpProvider.interceptors.push('redirectInterceptor');
    }]); 
user3714598
  • 1,733
  • 5
  • 28
  • 45
  • 4
    in angularjs 1.3 to intercept 302 status code i must use 'responseError' interceptor – Nemus Sep 30 '15 at 10:04
  • 20
    Just a note to help people understand this -- this technique is needed not because of anything Angular is doing, but because of the XHR browser api. Redirection is handled by the browser and is entirely invisible to the Javascript layer; an XHR request's headers, body and status are always those of the *final* response. Thus you can only know if you were redirected with prior knowledge that can be used to evaluate the content of the response. One technique to work around this is to have your server API explicitly include redirect info in responses that are known to be part of a redirect chain. – Semicolon Oct 06 '15 at 17:23
  • **Be careful**. this potentially could be a security hole in your application. Since `$http` is used to fetch various kinds of data and if any other request data contains text `My Login Page`, you application starts to behave incorrectly – Vohidjon Karimjonov Mar 21 '17 at 10:57
  • 1
    If I get this correctly if I send my auth token in a header to my service and it redirects to a 3rd party, the redirect automatically inherits the headers I added to the original request. This might be a security hole if I get it right @Semicolon – fodma1 May 16 '17 at 09:32
2

I ran into this problem as well, trying to find a nice way of implementing that a user be prompted to change his password, when logging in for the first time.

The way I solved this problem is by letting the server return a (unofficial) 210 status code with a location header, containing the information needed for the UI-Router's state provider. In the controller at the front I have added:

if (data.status == 210) {
  var location = data.headers().location;
  $state.go(location);
} else {
  $state.go("user", {name: data.data.fullName, userId: data.data.userId});
}
Albert Waninge
  • 319
  • 1
  • 10
1

I think there is a better way than the http interceptor for login redirection because it parses the request response and if another page has the string requested, an error will occur.

I suggest wrapping $http methods in your service and check if the user is logged before making the request:

app.service('httpWrapper', ['$http','requestLogin',function($http,requestLogin){
  var httpWrapper = {};

  // You can add methods for put, post
  httpWrapper.get = function(url){
    // Before making the request, check with a request if the user is logged
    $http.get(urlLogged).success(function (auth) {
        if(!auth.logged){
            // Show login widget
            requestLogin.show();
        }
    }).error(function(){

    });        
    return $http.get(url);
  }

  return httpWrapper;
}]);

Then uses httpWrapper in place of $http in your other services:

app.service('otherService', ['httpWrapper',function(httpWrapper){
  httpWrapper.get(url);
}]);

Here the service for requestLogin:

app.service('requestLogin', ['$rootScope',function($rootScope){
  var requestLogin = {};

  $rootScope.showlogin = false;

  requestLogin.show = function(){
    $rootScope.showlogin = true;
  }

  requestLogin.hide = function(){
    $rootScope.showlogin = false;
  }    
  return requestLogin;
}]);

And then, in your view:

<div ng-show="showlogin == true">
  Please log-in...
</div>
Gautier Drusch
  • 701
  • 5
  • 16