Felix D.
Felix D.

Reputation: 2220

Interceptors for managing restricted pages in AngularJS

I just started a few days ago with AngularJS and I'm having issues with my interceptor that intercepts 401 statuses from server responses.

It broadcasts a message of the type "loginRequired" when a 401 is returned and a redirect is triggered on that event.

The issue is that if I try to access a restricted page while not being logged in, I can see the page flash for a moment before I'm redirected to the login page. I'm still fairly a beginner in asynchronous stuff, promises etc. Could somebody point out what I'm doing wrong?

Here's my interceptor. As you can see it's really simple but I slimmed it down to explain my point and I'm trying to understand things before developing it further.

The interceptor

var services = angular.module('services', []);

services.factory('myInterceptor', ['$q', '$rootScope',
  function($q,$rootScope) {

    var myInterceptor = {

      'responseError': function(rejection) {
        $rootScope.$broadcast('event:loginRequired');
        return $q.reject(rejection);
      }
    };

    return myInterceptor;
  }
]);

The injection of my interceptor

myApp.config(['$httpProvider', function($httpProvider) {
  $httpProvider.interceptors.push('myInterceptor');
}]);

The route for the restricted page

.when('/restrictedPage', {
  templateUrl: 'partials/restrictedPage.html',
  controller: 'RestrictedPageController'
}).

The restricted page controller

controllers.controller('RestrictedPageController', function($scope) {

  //Some times the alert pops up, sometimes not.
  alert("Damn it I shouldn't be there");

});

The $rootScope event watcher

$rootScope.$on('event:loginRequired', function() {

  //Only redirect if we aren't on free access page
  if ($location.path() == "/freeAccess")
    return;

  //else go to the login page
  $location.path('/home').replace();

});

My issue is clearly with the way I handle the interceptor and $q. I found another way of creating the interceptor on github but it's not the way the official documentation uses, so I think it might be the old way and it's not as clean as putting it in a factory in my opinion. He just puts this code after defining the routes in the config function of his module. But this code works and I don't get the page flash.

Another way I found on Github

var interceptor = ['$rootScope', '$q', '$log',
  function(scope, $q, $log) {

    function success(response) {
      return response;
    }

    function error(response) {
      var status = response.status;

      if (status == 401) {
        var deferred = $q.defer();
        var req = {
          config: response.config,
          deferred: deferred
        };
        scope.$broadcast('event:loginRequired');
        return deferred.promise;
      }
      // otherwise
      return $q.reject(response);

    }

    return function(promise) {
      return promise.then(success, error);
    };

  }
];
$httpProvider.responseInterceptors.push(interceptor);

But my goal is not just to "make it work" and I hate the mantra "If it's not broken don't fix it". I want to understand what's the issue with my code. Thanks!

Upvotes: 1

Views: 1533

Answers (1)

Andrew
Andrew

Reputation: 5715

Instead of broadcasting 'event:loginRequired' from your interceptor, try performing the location path change within your interceptor. The broadcast would be increasing the delay between receiving the 401 and changing the location and may be the cause of the screen 'flash'.

services.factory('myInterceptor', ['$q', '$rootScope', '$location',
  function($q, $rootScope, $location) {

    var myInterceptor = {

      'responseError': function(rejection) {
         if (response.status === 401 && $location.path() !== '/freeAccess') {
           //else go to the login page
           $location.path('/home').replace();
         }
         // otherwise
         return $q.reject(response);
      }
    };

    return myInterceptor;
  }
]);

You could also perform a HTTP request when your app module first runs to determine right away if the user is authorised:

myApp.config(['$httpProvider', function($httpProvider) {
  $httpProvider.interceptors.push('myInterceptor');
}])
.run(function($http) {
  //if this returns 401, your interceptor will be triggered
  $http.get('some-endpoint-to-determine-auth'); 
});

Upvotes: 2

Related Questions