Protecting Angular Routes: Exploring the Power of canLoad and canActivate for Secure and Efficient Navigation
roberteftene
Posted on January 21, 2024
Navigating a web application involves more than just creating links and rendering content for the user. Most of the times, as developers, we need to enforce certain conditions or permissions before allowing users to access specific parts of our application. One way of accomplishing this is by using route guarding tools, and Angular provides us with powerful instruments for this purpose: canLoad
and canActivate
.
We will delve into the world of route guarding in Angular, focusing on the distinct roles played by canLoad
and canActivate
. These interfaces enable us to enhance the security of the applications and optimise the content loading. Therefore, this article will help you understand their exact applications, implementations and the scenarios where each shines.
Introducing to canLoad
The main purpose of canLoad
is to prevent lazy-loaded modules from loading in the browser if certain conditions are not met. As an example, let’s say we have an app for teachers and students, and we want to load the teachers module in a lazy manner, but only if the user have the role of a teacher. Here comes into play canLoad
, which will load the module only if the user is a teacher. In this way, besides securing the content, we are also optimising the application by not loading unnecessary modules.
For demonstration purposes, let’s continue with the teacher/student platform. We will define the routes of our application like this:
const routes: Routes = [
{
path:'',
children: [
{
path:'student',
loadChildren: () => import('./modules/student/student.module').then((m) => m.StudentModule),
//assign route guard
canLoad: [UserCheckGuardService],
//we are passing the role required for accessing this module
data: [{
roles: 'student'
}],
},
{
path: 'teacher',
//assign route guard
canLoad: [UserCheckGuardService],
data: [{
roles: 'teacher'
}],
loadChildren: () => import('./modules/teacher/teacher.module').then((m) => m.TeacherModule)
},
{
path: 'social-events',
loadChildren: () => import('./modules/public/public.module').then((m) => m.PublicModule)
},
{
path: 'login',
loadComponent: () => import('./shared/login/login.component').then((c) => c.LoginComponent)
}
]
}
];
Note that we are using canLoad
property for providing the guard for the student and teacher modules. We also defining the role required for accessing the route, in the data
property. What we are passing to the canLoad
property is an implementation of the CanLoad
interface, UserCheckGuardService
.
@Injectable({
providedIn: 'root'
})
export class UserCheckGuardService implements CanLoad {
private readonly router = inject(Router);
private readonly authService = inject(AuthService);
constructor() {
}
canLoad(route: Route, segments: UrlSegment[]): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
if (this.authService.getCurrentUser()) {
if (route.data[0].roles === this.authService.getCurrentUser()?.role) {
return true;
}
}
return this.router.navigate(['login'])
}
}
In this class, I implemented the logic for the canLoad
method, which will be called before initial loading of the module. The method checks if the current user has the required role for accessing the route. If yes, the module is loaded; otherwise, the user will be redirected to the login page. In this way we are only loading the module when we are suppose to do it.
Note: Once a module is loaded, the canLoad
guard will not be triggered again. canLoad
is specifically designed to control the initial loading of a module. However, it's crucial to be aware of a potential risk. If a user logs out and a different user logs in, attempting to access an already loaded module, the canLoad
guard won't be invoked again. Consequently, this can lead to a security vulnerability, allowing the user to bypass the guard. To address this scenario we will use canActivate
.
Another thing to keep in mind is that, at the moment of writing this article, canLoad
is not working for standalone
components. canActivate
can be used instead.
Introducing to canActivate
The canActivate
interface plays a pivotal role in route guards, operating before a route is activated. This implies that every time a user attempts to access a route, the associated guards are triggered.
For our scenario, I’ve created StudentRouteGuard
service which is implementing canActivate
interface:
@Injectable({
providedIn: 'root'
})
export class StudentRouteGuardService implements CanActivate {
private readonly router = inject(Router);
private readonly authService = inject(AuthService);
constructor() {
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
if (this.authService.getCurrentUser()) {
if (this.authService.getCurrentUser()?.role === 'student') {
return true;
}
}
return this.router.navigate(['login'])
}
}
Having a similar code implementation with UserCheckGuardService
, the StudentRouteGuard
will check, each time the route is activated, if the current user has the role of a student.
Next, we are adding this guard to the desired route path.
const routes: Routes = [
{
path: '',
component: StudentOverviewComponent,
// added canActivate implementation for securing StudentOverview view
canActivate: [StudentRouteGuardService]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class StudentRoutingModule { }
With this setup, even if the module was previously loaded (and canLoad
is no longer triggered), canActivate
will activate on every attempt to access the route, redirecting users with incorrect roles to the login page.
As a conclusion, it’s important to note that the combination of canLoad
and canActivate
is the key of achieving a robust security strategy. While canLoad
it’s optimising initial module loading, canActivate
ensure continuous route-level security.
Key Notes
-
canLoad
is useful when we want to prevent unnecessary loading of lazy-loaded modules, therefore, provides a performance boost -
canLoad
is triggered only at the initial loading of the module. Once the module is loaded, the guard will not be called anymore. -
canLoad
works only for lazy-loaded modules -
canActivate
runs before activating a route and provides robust security at both module and route levels
Thanks for reading! Feel free to leave any questions or feedback!
Happy coding! And may your Angular routes always be secure and efficient!
Posted on January 21, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.