Web Development

How to Auth Lazily-Loaded Routes in Angular

July 21st, 2020 | By Chidume Nnamdi | 4 min read

Explore how to Auth Lazily-Loaded Routes in Angular.

Angular has built-in authentication mechanisms for protecting routes in our app. These are called Route Guards. You might recall we did a tutorial recently about creating a secure role-based app using Angular route guards, so feel free to check out that tutorial as well.

We will cover a specific security problem that will arise when you’re trying to add lazily-loaded routes in Angular.

Let’s start with a little bit of background.

Route Guards is a feature of the @angular/router module that provides interfaces and methods to reject or allow navigation to routes.

There are five different types of Guards:

  • CanActivate: checks to see if a user can visit a route.

  • CanActivateChild: checks to see if a user can visit a route’s children.

  • CanDeactivate: checks to see if a user can exit a route.

  • Resolve: performs route data retrieval before route activation.

  • CanLoad: checks to see if a user can route to a module that is lazy loaded.


As we’ll see in a minute, only the CanLoad guard protects lazy-loaded routes.

Let's say we have our routes configured like this:

const routes: Routes =
[
    {
        path: 'admin',
        component: AdminComponent
    },
    {
        path:'dashboard',
        component: DashboardComponent,
        loadChildren: ()=> import("./dashboard/dashboard.module").then(m => m.DashboardModule)
    }
]


We have two routes or paths here: 'admin' and 'dashboard'.

The 'dashboard' route is lazy-loaded, which means that the route will not be loaded with the bundle on the initial load of the Angular app. It will be loaded only when the user navigates to that route.

The dashboard is a lazy-loaded route because of the loadChildren property there. It is a function value that runs when the route is navigated to. This function uses the import function to load its component module (DashboardModule), which contains components and other Modules, Components, Directives, and Services attached to this module.

The function returns a Promise that resolves to the NgModule instance of the module.

We can protect the admin route by using the CanActivate guard:

@Injectable()
export class AdminAuthGuard implements CanActivate, CanActivateChild {...}

const routes: Routes =
[
    {
        path: 'admin',
        component: AdminComponent,
        canActivate: [AdminAuthGuard]
    },
    {
        path:'dashboard',
        component: DashboardComponent,
        loadChildren: ()=> import("./dashboard/dashboard.module").then(m => m.DashboardModule)
    }
]


But we can't use it to protect lazy-loaded routes like the dashboard route.

@Injectable()
export class AdminAuthGuard implements CanActivate, CanActivateChild {...}

@Injectable()
export class AuthGuard implements CanActivate, CanActivateChild {...}

const routes: Routes =
[
    {
        path: 'admin',
        component: AdminComponent,
        canActivate: [AdminAuthGuard]
    },
    {
        path:'dashboard',
        canActivate: [AuthGuard]
        component: DashboardComponent,
        loadChildren: ()=> import("./dashboard/dashboard.module").then(m => m.DashboardModule)
    }
]


It just won't work. Navigating to the dashboard will load the DashboardModule without any security checks.

To auth lazily-loaded routes, we will use the CanLoad guard, as recommended in the Angular documentation.

We will create a guard class that will implement the CanLoad interface and define a canLoad method:

@Injectable()
export class AuthGuard implements CanLoad {

  constructor(...) {}

  checkTokenExpiration() {
      // checking whether your token has expired
      ... implementation here
      return false || true
  }

  canLoad() {
    return this.checkTokenExpiration()
  }
}


Our AuthGuard implemented the CanLoad interface and now has a canLoad method. The canLoad checks for token expiration via the checktokenExpiration method. Just like canActivate and canActivateChild, canLoad returns true if a route is to be loaded or false if it is not to be loaded.

Now, we will remove canActivate from the dashboard route and add canLoad:

...

const routes: Routes =
[
    ...,
    {
        path:'dashboard',
        canLoad:[AuthGuard],
        component: DashboardComponent,
        loadChildren: ()=> import("./dashboard/dashboard.module").then(m => m.DashboardModule)
    }
]


Navigating to the dashboard would auth in AuthGuard#canLoad. If it returns true, the Module is loaded; if not, navigation is denied, which is precisely what we were looking for.

So, we see that the CanLoad guard is used to authenticate, protect, or authorize navigation on lazily-loaded routes in Angular.

Problem

Still, we are faced with a problem. While CanLoad auths lazily-loaded routes, once the Module is loaded, CanLoad will no longer auth that route again.

Think of this example: a user is logged in and authenticated in your Angular app and navigates to the dashboard route for the first time. It will pass, and the Module will load. Now, the user logs out of the app; he is not authenticated anymore. But, when he navigates to the dashboard route during his logged-out state, the Module will load! Remember, he is already logged out, not auth anymore—yet, the dashboard route loaded. This is a security breach.

This happened because the DashboardModule route was already loaded at the initial navigation to the route when the user was authenticated. The CanLoad guard doesn't work anymore once the Module of the route it protects is loaded. The route will have to be authenticated by a canActivate guard.

To patch this, we need to include a canActivate guard on the lazy route.

...

const routes: Routes =
[
    ...,
    {
        path: 'dashboard',
        canLoad: [ AuthGuard ],
        canActivate: [ AuthGuard ],
        component: DashboardComponent,
        loadChildren: ()=> import("./dashboard/dashboard.module").then(m => m.DashboardModule)
    }
]


Thanks to this patch, CanLoad will work on the initial navigation and CanActivate will kick in on further navigations after the initial navigation. Security breach avoided!

Conclusion

Authentication in Angular is broad.

Thanks to an amazing effort by the Angular team, we have built-in strong features for route and HTTP request validation. Still, nothing is 100% secured, and we will still have to make a lot of security design decisions ourselves.

Improve the security of your Angular apps even further and prevent code theft and reverse-engineering.

Jscrambler

The leader in client-side Web security. With Jscrambler, JavaScript applications become self-defensive and capable of detecting and blocking client-side attacks like Magecart.

View All Articles

Must read next

Web Development

Handling CPU-Intensive Work Using Web Workers in Angular

Web workers are great for improving the performance of web applications. They can be used in Angular to handle CPU-intensive tasks.

August 6, 2020 | By Jay Raj | 6 min read

Web Development

Getting Started with Observables in Angular

Let's put Angular Observables under the microscope - understanding how they handle data streams and seeing them in action in some example scenarios!

November 27, 2019 | By Ajdin Imsirovic | 6 min read

Section Divider

Subscribe to Our Newsletter