In my previous post I explained how to set up windows authentication in an Angular application.

In this post I will explain how to implement authorization in the application based on the authenticated user.

I’m starting this post with the code base of the previous post.

Extending the Web API

I’ll extend the API with a new model, AppUser, which will contain the user information that is necessary for Authorization in the application.

public class AppUser{public string DomainName { get; set; }public string Role { get; set; }}

Next I will change the WinAuthController. I’m returning an instance of the AppUser object when the user is successfully authenticated.

[HttpGet][Route("login")]public AppUser Authenticate(){Debug.Write($"AuthenticationType: {User.Identity.AuthenticationType}");Debug.Write($"IsAuthenticated: {User.Identity.IsAuthenticated}");Debug.Write($"Name: {User.Identity.Name}");if (User.Identity.IsAuthenticated){//return Ok(new AppUser { DomainName = User.Identity.Name, Role = "Admin" });return new AppUser { DomainName = "domain\\yourusername", Role = "Admin" };}else{throw new Exception("Not Authorized");}}

Typically, you would want to load the information from a database or something. To keep things simple, I’m just returning the domain name and a fixed role.

Extending the Angular application

In the Angular application, I’m moving the AuthenticationService into a separate Angular module, which I’m calling ‘debiese.security’. I’ll also add an AuthorizationService to the module. Again, to keep things simple, I also added a separate file for declaring the module. You shouldn’t do that in production code.

NOTE: Keep in mind that the Angular module should exist before the services are added to it. So the order of loading them in the application matters!

My security module:

As I said, the securityModule is solely for declaring the debiese.security Angular module.

angular.module('debiese.security', []);

Both the AuthenticationService and the AuthorizationService are added to this module.

angular.module('debiese.security').service(S.AuthenticationService.id, S.AuthenticationService);.service(S.AuthorizationService.id, S.AuthorizationService);

I’m creating this separate module because I want to access the Authentication- and Authorization Services in the application runtime (app.run).

AuthenticationService

Let’s have a look at the changes in the AuthenticationService first.

export interface IAuthenticationService {authenticatingPromise: ng.IPromise<any>;authenticate: (forceAuthentication?: boolean) => ng.IPromise<M.IAppUser>;getUser: () => M.IAppUser;isAuthenticated: () => boolean;isAuthenticating: () => boolean;}

A lot has changed compared to my previous sample.

  1. getUser
    Returns an AppUser object when the authentication was successful. Otherwise it returns null.

    getUser(): M.IAppUser {const self = this;return self._identity;}
  1. isAuthenticted
    Returns true when authentication was successful. Otherwise it returns false.

    isAuthenticated(): boolean {const self = this;return self._identity != null;}
  2. isAuthenticating
    Returns true when the authentication is in progress. Otherwise it returns false.

    isAuthenticating(): boolean {const self = this;return self._isAuthenticating;}
  3. authenticate
    This is where it all happens.The most important part is calling the WinAuthController on the Web API to authenticate. But I also want to provide some information on the AuthenticationService to know if the user is already authenticated.If for some reason the authenticate method is called a second time, we can immediately provide the result without calling the Web API again. Unless of course I want to force a call to the Web API by providing the forceAuthentication parameter.When I initiate the authentication by calling the Web API, the _isAuthenticating variable is set to true. Also the variable authenticatingPromise is initiated. I do this so that I can call isAuthenticating and if that returns true, I can write code to execute when the authenticatingPromise resolves.

    authenticate(forceAuthentication?: boolean): ng.IPromise<boolean> {const self = this;let defer = Q.defer();if ((!self.isAuthenticated && !self._hasAuthenticated) || (forceAuthentication != null && forceAuthentication === true)) {let serviceUrl: string = `${C.Configuration.AppConfig.serviceBaseUrl}auth/login`;self._hasAuthenticated = false;self._isAuthenticating = true;//Initiate promise that will resolve when authentication process is complete.//This is usefull when authorization is being checked when the authentication is not yet complete.let authDefer = Q.defer();self.authenticatingPromise = authDefer.promise;self.$http.get(serviceUrl).success((data, status, headers, config) => {self._identity = data as M.IAppUser;defer.resolve(true);}).error((data, status, headers, config) => {self.logSvc.log(data);defer.reject(new M.RejectMessage({ Message: data }));}).finally(() => {self._hasAuthenticated = true;self._isAuthenticating = false;//Let any code waiting for the authentication to finish know that authentication finished!authDefer.resolve();});} else {defer.resolve(self.isAuthenticated());}return defer.promise;}

AuthorizationService

export interface IAuthorizationService {isAuthorized: (requiredRole: string) => boolean;}

I’m implementing a fairly easy isAuthorized method in which we check if the authenticated user has a specific role. This can be as complicated as you like, but in this example, this simple check will suffice.

UI-Router configuration

With the Authentication- and Authorization services in place, I can start using this to add authorization on the router configuration. I’ll be using the data object on the state configuration to configure which states require the user to be authenticated and which role a user needs to have to be able to access a state.

I created the StateSecurity object to keep things together.

export class StateSecurity implements IStateSecurity {LoginRequired: boolean;Role: string;constructor(obj?: IStateSecurity) {if (obj != null) {this.LoginRequired = obj.LoginRequired;this.Role = obj.Role;}}}

The states in the application:

$stateProvider.state('home', {url: '/home',templateUrl: '/app/components/home/homeView.html',controller: DeBiese.Authorization.NG.Controllers.HomeController.id,controllerAs: 'vm',data: {security: new M.StateSecurity({LoginRequired: true,Role: 'Admin'})}}).state('help', {url: '/help',templateUrl: '/app/components/help/helpView.html',controller: DeBiese.Authorization.NG.Controllers.HelpController.id,controllerAs: 'vm',data: {security: new M.StateSecurity({LoginRequired: true,Role: 'User'})}}).state('loading', {url: '/loading',templateUrl: '/app/components/loading/loadingView.html'}).state('unauth', {url: '/unauth',templateUrl: '/app/components/authorization/notAuthorizedView.html',controller: DeBiese.Authorization.NG.Controllers.NotAuthorizedController.id,controllerAs: 'vm'});

Two states require the user to be authenticated, the other 2 don’t. Obviously the Not Authorized state should not require the user to be authenticated. The loading state will be discussed later on in the post.

Implementing the authorization

Adding the StateSecurity objects to the state doesn’t do anything in itself. I need to enforce usage of that information myself. The way to do this, is by intercepting the stateChangeStart event that is broadcasted on the $rootScope by the ui-router.

$rootScope.$on('$stateChangeStart',(event: any, toState: angular.ui.IState, toParams: angular.ui.IStateParamsService, fromState: angular.ui.IState, fromParams: angular.ui.IStateParamsService) => {//Implementation here});

When a state change is initiated I can check the data object on the state, and if a StateSecurity object is available I’ll inspect it and if necessary use it for authorization. If not, the state change can continue without performing additional actions on it.

if (toState.data != null && toState.data.security != null) {//Implementation here}

If StateSecurity is configured on a state, but during the state change the authentication process is still running (I provided the isAuthenticating method for this), I don’t want the state change to take place. So I prevent the default action (the change of state) and write code to run when the authenticatingPromise on the AuthenticationService resolves. I simply re-initiate the state change with the same parameters. In the meantime, however, I need to make sure that something is loaded. That’s why I provided the loading state.

//If authentication is still in progress, prevent navigation and retry when authentication is completeif (authenticationSvc.isAuthenticating()) {//Navigate to unprotected route for the time being (prevents infinite digest loop)//Might cause slight flicker on screen when authentication is quick.$state.go('loading');event.preventDefault();//Stave navigation when authentication is finishedauthenticationSvc.authenticatingPromise.then(() => {$state.go(toState, toParams);});}

Let me explain that in a bit more detail. If I prevent the default action on a state change without initiating a new state transition, the application will then try to load the default route. The default route will trigger a state change to the home state. This state has a StateSecurity object and as we already know, the AuthenticationService is still authenticating. So this would trigger an infinite digest loop. The way around this behavior is to initiate a state change myself instead of just preventing the original state change and make the transition to a state that has no StateSecurity object. When the authenticatingPromise resolves, the original state change is initiated.

This leaves the situation where a state change is initiated, a StateSecurity configuration is in place and the authentication process is completed. I check if a login is required and if so whether or not the logged in user has the necessary role to access the desired state. If not, a NotAuthorized state is loaded.

//If authentication is still in progress, prevent navigation and retry when authentication is completeif (authenticationSvc.isAuthenticating()) {//...}else {let security: M.IStateSecurity = toState.data.security as M.IStateSecurity;if (security.LoginRequired && !authorizationSvc.isAuthorized(security.Role)) {$state.go('unauth');event.preventDefault();}}

In the sample application, I return the Admin role for every user. This means that every user is allowed to access the Home state, but not the Help state which requires a role with the name User.

Summary

  • Security module
    • AuthenticationService
    • AuthorizationService
  • UI-Router configuration
    • StateSecurity configuration
  • App.run
    • Initiate authentication
    • Intercept stateChangeStart for authorization

Closing word

I hope the provided information gives you some insight into the authorization process. Keep in mind that this is all done in Typescript (javascript) and can be easily manipulated by a tech-savvy user. This means that on the Web API, we also have to implement authorization on actions that can be called from pages I configured to need authorization in the application. It’s out of scope for this blog post, but you should not overlook this when building a real world application.

The full code base of this little test project can be found on GitHub.

Feel free to comment down below!

 

Ruben Biesemans

Analyst Developer @ Spikes