Role Based Authorization in Angular – Route Guards

In this post, we are going to use Route Guards to determine which user can and can not access certain pages. It is common to have multiple user roles such as guest, author, editor, admin for a blogging site such as this one. Therefore, it is important to determine who has access to what routes, so as to provide a good user experience (UX).

NB: For security purpose, this should not be used as the only solution, similar security measures needs to be implemented at the backend server. This is because, other developers and hackers can be able to manipulate the frontend app to gain access. This should be used mainly to provide a consistent and good user experience to the end user.

In this post, we shall use JSON Web Tokens (JWTs), which will contain the user role of the current user. The JWT token, shall have a user role added to it on the server, after signing in together with other user details such as email, username, full name etc.

This post assumes that you have a backend ready and working.

Why JWTs?

JSON Web Tokens, has three sections, the header, payload and signature. The header contains information about the token and hashing algorithm used, while the payload contains information such as user information, expiry time and any information you wish to add.

The Signature part is why we are using JWTs. It secures the token, ensuring that if someone – say a frontend user – tampers with it, you will know. It is a hash of the encoded header and payload, along with a secret which only you should know. This makes it perfect, for authentication and authorization purposes, passing information to the client that is tamper proof.

This ensures that, if someone were to change the token on the frontend to beat the Route guard, the server would know if they tried to act using the token they modified, to change something. This of course relies on the fact that the secret key or password used to sign the token remains secret. You can learn more about JWTs here.

Getting Started

We will start by creating a new project in angular, with support for routing.

$ ng new angular-role-based-authorization --routing true

Then, we need a library to read JWT Tokens in Angular. For that, we will use angular2-jwt by Auth0. Let’s add it in our app and add it to our list of imports in our app module. So, first install it using your favorite package manager:

Using NPM:

$ npm install @auth0/angular-jwt

Using Yarn:

$ yarn add @auth0/angular-jwt

Next, add it to the list of imported modules in our app module:

...
import { JwtModule } from '@auth0/angular-jwt';
...

@NgModule({
  declarations: [ ... ],
  imports: [
    ...
    JwtModule.forRoot({}),
    ...
  ],
  providers: [...],
  bootstrap: [AppComponent]
})
export class AppModule {}

Routes

Our first action will be to create routes for our demo app. We shall embed the roles required by each of the routes on the route object. This way, each route shall be aware of the roles it will allow access to it and you can just check the user role against allowed roles:

{
  path: 'author',
  component: AuthorComponent,
  data: {
    allowedRoles: ['admin', 'author']
  }
}

So, in the case of author route, we shall allow users with author and admin role. The same would go for writer route, where we shall allow admin, writer and author.

{
  path: 'writer',
  component: WriterComponent,
  data: {
    allowedRoles: ['admin', 'writer', 'author']
  }
}

The reason for embedding roles on the routes, is to enable us to use a single route guard for all our routes. Removing the need for repetition.

Authorization Service

Next, we need to create a service to determine whether a user role is allowed to access a route. We shall read the user role from the JWT Token, then check whether the role is in the list of allowed Roles, contained in the data. If the list of allowed roles is empty, we shall grant user access but only deny them access if their role is not listed in the allowed list.

First, let’s create a new service:

$ ng generate service authorization

Then inject the JwtHelperService service into our service, so we can decode tokens:

constructor(private jwtHelperService: JwtHelperService) {}

Then, add a method to check if the user is authorized. It will accept allowed roles string array and check whether current user role is among the roles that are allowed:

isAuthorized(allowedRoles: string[]): boolean {
  // check if the list of allowed roles is empty, if empty, authorize the user to access the page
  if (allowedRoles == null || allowedRoles.length === 0) {
    return true;
  }

  // get token from local storage or state management
  const token = localStorage.getItem('token');

  // decode token to read the payload details
  const decodeToken = this.jwtHelperService.decodeToken(token);

  // check if it was decoded successfully, if not the token is not valid, deny access
  if (!decodeToken) {
    console.log('Invalid token');
    return false;
  }

  // check if the user roles is in the list of allowed roles, return true if allowed and false if not allowed
  return allowedRoles.includes(decodeToken['role']);
}

Next, add the service to the list of providers in the app module:

@NgModule({
  declarations: [
    ...
  ],
  imports: [
    ...
  ],
  providers: [AuthorizationService],
  bootstrap: [AppComponent]
})
export class AppModule {}

Route Guard

Next, let’s add a route guard to our app.

ng generate guard authorization

Then, let’s inject our authorization service into the route guard.

constructor(private authorizationService: AuthorizationService, private router: Router) {}

We also need to implement the CanActivate and CanActivateChild interfaces, which will check whether a user is authorized.

export class AuthorizationGuard implements CanActivate, CanActivateChild {
  canActivate(next: ActivatedRouteSnapshot,state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean { }
  canActivateChild(next: ActivatedRouteSnapshot,state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean { }
}

Then, let’s add the two methods to our router guard. They will have similar content; the only difference is that, one checks if a route can be activated while the other checks if children routes can be activated. So, inside both two methods, we need to first get the allowed roles for that route:

const allowedRoles = next.data.allowedRoles;

Then, check if the user is authorized to access the route:

const isAuthorized = this.authorizationService.isAuthorized(allowedRoles);

if (!isAuthorized) {
  // if not authorized, show access denied message
  this.router.navigate(['/accessdenied']);
}

return isAuthorized;

So, I our canActivate method will look like this:

canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
  const allowedRoles = next.data.allowedRoles;
  const isAuthorized = this.authorizationService.isAuthorized(allowedRoles);

  if (!isAuthorized) {
    this.router.navigate(['accessdenied']);
  }

  return isAuthorized;
}

And canActivateChildMethod:

canActivateChild(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
  const allowedRoles = next.data.allowedRoles;
  const isAuthorized = this.authorizationService.isAuthorized(allowedRoles);

  if (!isAuthorized) {
    this.router.navigate(['accessdenied']);
  }

  return isAuthorized
}

Now, let’s add the guard to our parent route:

const routes: Routes = [
  {
    path: '',
    canActivateChild: [AuthorizationGuard],
    children: [
      // routes here
    ]
  }
];

Demo and Source Code

And that’s it, we have a role-based authorization for our angular application. You can visit the following link to see and play with the demo. You can also find the source code here.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.