Thank you for reading my blog posts, I am no longer publishing new content on this platform. You can find my latest content on either mainawycliffe.dev or All Things Typescript Newsletter (✉️)
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.
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.
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
// or 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 {}
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.
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 {}
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
]
}
];
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.
In this post, we are going to build a http interceptor for refreshing authorization tokens once expired. The idea here is to be able to …
Read MoreOne of the least talked about features of Angular 6 is Angular CLI Workspaces. Workspaces or Angular CLI Workspaces give angular developers …
Read MoreA few weeks ago, I demonstrated how to implement Sign in with Facebook with a REST API. Today, I will show you how to Sign-in with Google in …
Read MoreToday, I am going to show you how to create an Angular App with a REST API that uses Facebook Login to sign in/up users. For this …
Read MoreToken based authentication is popular for single page applications. A token is a security code issued by a server for authenticating and …
Read MoreIn a previous post, we covered how to use multiple dockerfile to target different environments for an angular app. In this post, we are …
Read More