Route Guards in Angular
Angular router API provides a seamless experience for navigating between screens. It improves the User Experience story in a lot better way. Generally, we show most screens if a user is logged in. Such use cases can be handled by writing a code to check whether the user is logged in and, if not, then redirect to the login page. This code would work, but it will be complex to maintain on each screen level in the long run. Would there be a better way to handle this situation?
To handle these kinds of situations, you can use route guards and the built-in router API way to solve this problem. As the name suggests, a Route guardacts as a gatekeeper for routes based on condition checks. Route guard can be useful in many use cases like role-based checks, authorized screens, conditional redirects, etc.
You should have an understanding of
- Angular Router
What are Route Guards?
Angular route guards are interfaces that allow us to control the access of a route. We can write a condition inside the class implementation. This enables extra protection for routes, whether to show route components or not. There are various interfaces exist to guard routes in different ways. On a single route, multiple route guards can be applied.
Note: Don't forget to add the created route guard service to the providers array in either specific NgModule or AppModule.
This interface can be implemented to guard a route and decide whether to activate the route or not. In case multiple guards are applied on the route, if all guards return true, then the route gets activated. If it returns false, then the current ongoing navigation is canceled. In some cases, we need to redirect from the canActivate method. That time you can return a URLTree from the canActivate implemented method.
PermissionGuard does check the permission inside implemented canActivate method. If the perimissions collection contains specified permission in route data permissionToCheck, then only it returns true and the respective route will load, otherwise not.
Note: CanActivate route guard in Angular is only called once when child routes are changed.
CanActivateChild is almost similar to the CanActivate interface. Suppose you wanted to guard all children's routes, you can easily add the CanActivate interface in all its children's routes. To be honest, that doesn't sound like an elegant solution. Keeping that in mind, Angular router API provided a CanActivateChild interface that helps to guard children's routes based on the defined condition. So you would have to use the CanActivate along with the CanActivateChild option on the route level.
In the above route configuration dashboard is a parent route. canActivate is used along with the canActivateChild option. PermissionGuard make sure that the user has admin permission. If not, then it doesn't load the dashboard route and its children's routes.
Note: CanActivateChild route guard is called whenever the user navigates between the child routes.
This route guard is useful in situations where the user is navigating away from the specific screen. For example, you can use this guard on
- User is leaving the edited form without saving it. We can show the "Do you want to save your changes?" modal to remind users to save changes.
- We can also use this guard to prevent users from back-browser action in certain situations.
The CanDeactivate route guard in Angular is a bit different in implementation from the other route guards as it involves calling a method defined on the component class whenever the user tries to navigate away from the route. Also, from this guard, you get early access to the next route state to see where it is navigating back to.
Here, T is an interface that we will be using for a component type.
Here is a home component that has the canDeactivate method, which helps to decide whether to leave a component or not. This method will be called UnsavedChangesGuard.
UnsavedChangesGuard implemented the CanDeactivate interface. canDeactivate has 1st parameter a HomeComponent. It calls the canDeactivate method of HomeComponent when a user tries to navigate to another page.
There are various strategies to load angular modules. Lazy loading and eager loading are the two main kinds. Angular defaulted the strategy to eagerly loading modules. A lazy loading strategy helps to load modules lazily. Lazy loading cannot check whether to load modules based on permission. CanLoad route guard implementation can help here.
It helps to prevent loading modules conditionally. So that unnecessary piece of code is not loaded at all in the browser. By using this CanLoad route guard in angular, we would get performance benefits.
It loads an angular module code only if the condition from the canLoad method is evaluated as a true.
PermissionLoadGuard does check the permission inside implemented canLoad method. If it returns true, then the respective module will load; otherwise not.
When the admin route is visited, AdminModule will only be loaded if the user has admin permission to view the screen.
The application state is an important piece of any application development. Mostly we had to preserve a user state. We also had to make sure, even though the user reloads the page, the user journey should not break and should resume from the same point where they were. In such cases, we need to preserve specific data; hence we put minimal information in the URL, like unique route, entity-id, etc. Based on these information sources, we make an ajax call to retrieve details from the API server. When the API calls take time, we would not show the screen, and rather we show the loading indicators. The Resolve route guard in angular helps to load the route until the promises/observables are resolved.
This can be used for
- Show loading until the promise is resolved.
- Route component doesn't render until the promise is resolved
T is a type of resolve that will pass to the component after resolving the promise/observable.
Routing Decisions Based on Token Expiration
Token expiration logic is completely based on AuthService. It maintains when to invalidate the token or renew the same. We can use the AuthService's isAuthenticated property to check whether or not the user is logged in and how we can control the screens to be displayed. CanActivate can be a better route guard implementation that we can utilize here.
The above AuthService implementation is a very general implementation of how the isLoggedIn flag has been maintained internally for logged-in users. This AuthService won't be used in real production applications, this is just a minimal version of auth service.
AuthGuardService implementation is pretty straightforward. It checks tokens by accessing the isAuthenticated property. If the token is not valid, then it creates an UrlTree, and the user will be navigated back to the login page. Otherwise, when true is returned, it proceeds with the current route and displays it.
Checking for a User's Role
Checking UserRole and Permission is always going to be a general use case when it comes to developing enterprise-grade applications. We can create RoleGuard similar to PermissionGuard with a small twist of redirection to the dashboard page when the role doesn't match.
A Protected Route - Is it Easily Hackable?
What do you think about the RoleGuard we created? Is it easily hackable? Currently, all the roles we retrieved use API calls from RoleService. An intruder can hack in between and modify the roles to get access to pages. To make routes more secure, we can put roles inside a JWT (JSON Web Tokens). You can decode and extract the payload from the jwt token. If someone tries to modify the roles or expiry_time inside jwt, and put it back inside a browser, modified jwt would have invalidated the signature. Hence it can not be decoded and processed further. Thus we made our routes more secure and less vulnerable.
Lock Routes Down Completely
To lock routes completely down
- CanActivateChild route guard
- CanLoad route guard
We have used AuthGuard in combination with PermissionGuard in both canActivate and canActivateChild to make sure routes should be activated.
- User is logged in.
- The user has the correct permission to access the route.
Additionally canLoad check the load module only if the user has permission.
- Manage protected routes.
- How can we prevent users from redirecting away from specific routes?
- How to keep routes canActivate logic non-hackable
- Use of resolve to wait for displaying the component until its data is loaded.