Protecting Angular Routes: Exploring the Power of canLoad and canActivate for Secure and Efficient Navigation
Tags: angular, webdev, frontend, typescript
Published: 2024-01-21 20:09:29

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:

```java
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`.

```java
@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: 

```java
@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. 

```java
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!