Search code examples
javascriptangularjshttpxmlhttprequestangular-http-interceptors

Authorize check access in Angular


I'm working in an Angular web app template taken by git. I can't understand a function that, I suppose, verify if the user has the authorization. Isn't it? My target is to have a function which make a login. Take the response and save the token. Then, when the $state changes, check if there still are the token. How can I do? This is the code:

core.js:

'use strict'; 

angular.module('app.core').controller('App', ['config', '$scope', '$state', '$rootScope', 'shortHistory','session', '$window', 'authorize', function(config, $scope, $state, $rootScope, shortHistory, session, $window, 'authorize') {

var vm = this;

vm.title = config.appTitle;

$scope.app = config;
$scope.$state = $state;
vm.currentUser = null;

$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
  authorize.checkAccess(event, toState, toParams);
});

$scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
  $('body').toggleClass('nav-shown', false);
});

$rootScope.$on('$userSet', function(event, user) {
  vm.currentUser = user;
  $window.localStorage.setItem('token', vm.currentUser.token);
});

shortHistory.init($scope);

}]);

common.js:

(function() {
'use strict';

angular.module('app.common')
  .service('shortHistory', shortHistory)
  .service('session', session)
 // .service('authorize', authorize)
  .service('authenticationService', authenticationService);

shortHistory.$inject = ['$state'];
function shortHistory($state) {
  var history = this;

  function setItem(what, state, params) {
    history[what] = {
      state: state,
      params: params
    };
  }

  this.init = function(scope) {
    scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){
      setItem('from', fromState, fromParams);
      setItem('to', toState, toParams);
    });
  };

  this.goTo = function(where) {
    $state.go(history[where].state.name, history[where].params)
  };
}

session.$inject = ['$http', '$q', '$rootScope'];
function session($http, $q, $rootScope) {
  var session = this;

  this.fetchCurrentUser = function(url) {
    var userFetch;
    if (session.getCurrentUser()) {
      userFetch = $q(function(resolve) {
        resolve(session.getCurrentUser());
      });
    } else {
      userFetch = $http.get(url);
    }
    return userFetch;
  };

  this.getCurrentUser = function() {
    return this.user;
  };

  this.setCurrentUser = function(user) {
    this.user = user;
    $rootScope.$broadcast('$userSet', this.user);
  };
}
/* THIS IS THE CODE THAT I'VE NOT TOUCHED, IT WAS IN THE TEMPLATE
authorize.$inject = ['session', '$state', '$urlRouter', '$rootScope'];
function authorize(session, $state, $rootScope) {
  this.checkAccess = function(event, toState, toParams) {
    if (!session.getCurrentUser() && !(toState.data && toState.data.noAuth)) {
      event.preventDefault();
      session.fetchCurrentUser('api/sers')
        .success(function(user) {
          session.setCurrentUser(user);
          $state.go(toState.name, toParams);
        })
        .error(function() {
          $state.go('login');
        });
      }
  };
}
*/
authenticationService.$inject = ['$http', '$rootScope', 'session', '$state'];
function authenticationService($http, $rootScope, session) {
  var vm = this;
  vm.loginError = '';
  this.login = function(user) {
    var userData = {
      email: user.email,
      password: user.password
    }
    return $http.post('api/login/', userData)
      .success(function(data) {
        var loggedUser = jQuery.extend(data, userData);
        session.setCurrentUser(loggedUser);
        $rootScope.$broadcast('$userLoggedIn');
      });
  };

  var token = localStorage.getItem('token');

  this.logout = function() {
    return $http.post('api/logout/', token)
      .success(function(data) {
        session.setCurrentUser(null);
        $rootScope.$broadcast('$userLoggedOut');
      });
  }
}

})();

Auth.js:

 (function() {
 'use strict';

 angular.module('app.profile')
  .controller('LoginController', loginController)
  .run(runAuth);

loginController.$inject = ['authenticationService'];
function loginController(
  authenticationService
  ) {
  var vm = this;
  vm.user = {};
  vm.loginError = '';

  this.login = function() {
    authenticationService.login(vm.user)
      .then(null, function(err) {
        vm.loginError = err.data.result;
      });
  };
}

runAuth.$inject = ['$rootScope', '$state', 'authenticationService'];
function runAuth($rootScope, $state, authenticationService) {
  $rootScope.logout = authenticationService.logout;
  $rootScope.$on('$userLoggedIn', function() {
    $state.go('app.dashboard');
  });
  $rootScope.$on('$userLoggedOut', function() {
    $state.go('login');
  });
}
})();

profile.module.js:

'use strict';

angular.module('app.profile', ['ui.router'])

.config(['$stateProvider', function($stateProvider) {
  $stateProvider
    .state('login', {
      url: '/login',
      data: {
        noAuth: true
      },
      templateUrl: 'app/modules/profile/auth/login.html',
      controller: 'LoginController',
      controllerAs: 'vm'
    })
    .state('app.profile', {
      url: '/profile',
      templateUrl: 'app/modules/profile/edit/edit.html',
      controller: 'ProfileController',
      controllerAs: 'vm'
    });
  }]);

The original project is this: https://github.com/flatlogic/angular-dashboard-seed I can't understand how that authentication works and where to insert the controller for the authentication. For example, right now, I can't do login but can't do the logout and even if I have no token I can correctly go to /dashboard, or /profile which should viewed by logged user only.


Solution

  • You have quite a bit of code here but I feel like your question can be answered more theoretically than with more code!

    • Use your backend server to set the session information (usually a cookie in the HTTP header) and then don't do any local manipulation in AngularJS code. I see some areas where you are checking local storage. Its easier and more secure to just let the backend set the HTTP only flag to true and build your app logic around not knowing the session id
    • Utilize an intercept to redirect 401 ajax requests to your login page. This will allow you to handle instances of session timeouts, unauthorized route visits, etc. A very simple handler that will simply much of the redirect logic https://docs.angularjs.org/api/ng/service/$http#interceptors
    • Depending upon your routing (it appears that you are using UI Router) you can manage permissions to particular routes based upon the user returned from your authentication service. The cleanest implementation I've encountered utilized UI Router and nested states. The states that require authentication have a root "resolve" parameter configured that performed a validation of the user's session (maybe just a simple HTTP GET to a protected endpoint to determine if the user has authenticated). You will truly appreciate this architecture when you need to know the user in any given controller and you can always know that your user service already has that figured out (and no need to rely on broadcasts)

    For example:

    $stateProvider
      .state('rootProtected', {
        templateUrl: '/js/views/rootProtected.html',
        resolve: {
          data: function(coreuser) {
            return user.initialize();
          }
        }
      })
      .state('rootProtected.userProfile', {
        templateUrl: '/js/views/userProfile.html'
      })
      .state('public', {
        templateUrl: '/js/views/login.html'
      });
    
    • Try not to create too many services/factories around session management. I see this in tutorials all over the internet. Really, just create a core user service that logs a user in, logs a user out, validates a user, gets a user, etc. You will start realizing that a lot of the data is interrelated and there is no reason to scatter logic amongst multiple services or broadcast events.