Using Angular Route Guards to Limit Access by Roles

Posted on November 07, 2018

angular authorization roles

Angular route guards are very popular for restricting access to parts of an application. Perhaps the most popular use of route guards is checking if the user is logged in period. The guard will check the authorization status of the logged in user, and if they are approved it returns true (or an observable that emits a true value at some point). After that, the application continues on with its execution.

But what happens after they've been confirmed to be authenticated, but they don't have a certain role necessary for part of your application? For example, let's say there's an admin section that only some people should see, but everyone that uses the app needs to log in to see it. In this case, a simple authentication guard isn't enough. We also need a guard that will check a user's account for one or more roles before activating a route.

I recently needed to accomplish this on a project, so let's look at how to do that. First, here's the route guard:

export class RouteCanActivateGuard implements CanActivate {

  constructor(private _permissions: PermissionsStorageService) {}

  canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot): any {
    const roles = next.data['roles'] as Array;

    return this._permissions.checkForAllRolesInArray(roles); // This goes and checks the server for the roles and returns an observable
  }
}

Essentially, this guard returns an observable from the PermissionsStorageService function checkForAllRolesInArray. That observable will eventually emit a boolean. If it's true, then the component activates. If it's false, then the component will not activate and will stay on whatever route it was on when the user tried to navigate.

Let's look, though, at the first line in the canActivate function. We see that we get an array of strings, which we call roles, from the next snapshot. That's the role that we check for. So where is that data set? Let's take a look:

const routes: Routes = [
  {
    path: '',
    component: LayoutComponent,
    children: [
      {
        path: 'home', 
        component: DashboardComponent, 
        canActivate: [AuthorizationGuard, RouteCanActivateGuard], 
        data: { roles: [ROLES.access]} 
      },
      { 
        path: 'some-path', 
        loadChildren: '../path/to/some-path.module#SomePathModule', 
        canActivate: [AuthorizationGuard, RouteCanActivateGuard], 
        data: { roles: [ROLES.access, ROLES.someRole]} 
      },
    },
];

First, we can see the canActivate array where we determine which route guards will be used. That, obviously, is where we'll list the RouteCanActivateGuard so that we can check for the roles we want. Right below that array is the data object that can be provided to each component and can be used in the guard. As an attribute of the data object, we have a roles array, which contains a list of roles which are required for the component to activate. Above I've used an enum to determine the roles that should be used, but you could just pass in strings here instead if you'd like.

As you can see, providing roles to the route guard which need to be checked before the route can activate is a pretty simple task. And without much effort, we could change the guard to only require one of the roles in the array, or any other variation that you may think of.