Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Demystifying Angular Route Guards: A Beginner's Guide to Secure Navigation

When building a web application, chances are that you will have some routing involved at some points.However, those routes might not be suitable for everyone. As an example, you might not want an anonymous user changing the profile page of someone else.Angular is no different from the (many) other frameworks and has to solve this issue.Hopefully, and thanks to its opinionated nature, those controls are baked-in into the framework. However, when getting started with them, it may be a bit obscure at first.In this article we will see what guards are, how to use them and see some concrete use cases through various scenarios where Route guards shine.So, whether you're interested in restricting access to certain routes, preventing unauthorized actions, or ensuring a smooth user experience, this guide has got you covered. Let's unlock the full potential of Angular route guards and take your application's routing capabilities to new heights! Table of ContentsUnderstanding Route GuardsClasses or Functions?The Different Types of Route GuardsAnatomy of a GuardCombining Route GuardsInlined GuardsTakeaways Understanding Route GuardsRoute guards are set of functions and classes that control and manage routing and navigation within your Angular application.They provide a way to protect routes, enforce certain constraints such as authentication or perform other checks on specific routes.As an example, some use cases that guards can help to manage could be:Preventing an anonymous user to see its profile pagePreventing a user without any basket to reach the checkout pagePreventing a regular user to access the administration panel Classes or functions?Not so long ago, guards where interfaces that needed to be implemented and registered in your modules.However, since the latest versions of Angular, class-based guards are deprecated in favor of functional guards.Instead of implementing an interface, it is now possible to use a function to achieve the same result.Class-based guards can still be used and easily converted into functional ones, thanks to the inject function:const myGuard: CanActivateFn = inject(CanActivateMyGuard).canActivate;There is even a PR that has been submitted to provide helper functions for that purpose. The different types of route guardsAs we previously mentioned it, there is a lot of different use cases for guards such as:Preventing a user to leave a page he has pending edits inAccessing unauthorized viewsFiltering logged in usersAnd much more.Even if those use cases involves routing, the intention is different in each case, that's why the Angular guard's API will also exposes different signatures:CanMatchFnCanActivateFnCanActivateChildFnCanDeactivateFnLet's break it down! CanMatchFnCanMatchFn is a guard to be used in the context of lazy loading.When evaluated, it will help Angular's router to determine whether or not the associated lazy-loaded route should be evaluated.If false, the bundle will not be loaded and the route will be skipped.⚗ Use it when you want to evaluate if the user can load a lazy-loaded route.🔭 ExampleAs a regular user, I should not be able to load the /admin route.const routes: Route[] = [ { path: 'admin', loadChildren: () => import('./admin').then(m => m.AdminModule), canLoad: [AdminGuard] },]; CanActivateFnCanActivateFn may be the more intuitive route guard: it helps to determine whether or not the current user can navigate to the route it decorates.There is no lazy-loading involved here, the route will be effectively loaded and evaluated by the router but its access could be prevented based on the guard's result.⚗ Use it when you want to prevent a user to access a given route.🔭 ExampleAs an anonymous user, I should not be able to see my /profile page until I am authenticated.const routes: Route[] = [ { path: 'profile', component: ProfileComponent, canActivate: [authenticationGuard] },]; CanActivateChildFnCanActivateChildFn is the same concept as CanActivateFn but applied to the children of the parent route.Given a route, evaluating this guard will indicate the router if you can access any of its children.⚗ Use it when you have a parent-children route hierarchy and you want to prevent access to those children but maybe not the parent🔭 ExampleAs a coach, I can see my team's details on /team/:id and edit it on /team/:id/edit.As a regular user, I can only see the team's details on /team/:id but cannot access the edit page on /team/:id/edit.const routes: Route[] = [ { path: 'team/:id', component: TeamDetailsComponent, canActivateChild: [teamCoachGuard], children: [ { path: 'edit', component: TeamEditComponent } ] },]; CanDeactivateFnThe CanDeactivateFn is slightly different than the other guards: other guards tend to prevent you from reaching a route, but this one prevent you from leaving it.There is no concerns about lazy loading here since the route is already loaded and activated.⚗ Use it when you want to prevent a user from losing data potentially tedious to type again or break a multi-steps process.🔭 ExampleAs a job candidate, I want to be prompted to confirm before navigating away from the page if my unsaved cover letter is not saved as a draft.const routes: Route[] = [ { path: 'online-application', component: OnlineApplicationComponent, canDeactivate: [unsavedChangesGuard] },]; Anatomy of a GuardGuards vary in purpose, but mainly follow the same structure. Given a route, it will either return (synchronously or not):A boolean to indicate whether the route can be reached (true if it can, false otherwise)Or an UrlTree designating a route to redirect the user toThe outputs are the same for all guards, however the input parameters may not. Before implementing the logic bound to a guard, check what its parameters are.To understand it, let's write a profileGuard which will:Redirect any anonymous user to /loginReject navigation for any user that would access the profile page of someone elseGrant navigation for the current user if he wants to browse his own profile pageconst profileGuard: CanActivateFn = ( route: ActivatedRouteSnapshot, state: RouterStateSnapshot,): | Observable | Promise | boolean | UrlTree => { const currentUser = inject(CurrentUserService).getCurrentUser(); // 👇 Redirects to another route const isAnonymous = !currentUser; if (isAnonymous) { return inject(Router).createUrlTree(["/", "login"]); } const profilePageId = route.params["id"]; // 👇 Grants or deny access to this route const attemptsToAccessItsOwnPage = currentUser.id !== profilePageId; return attemptsToAccessItsOwnPage;};Its usage is no different from our previous example:const routes: Route[] = [ { path: 'profile', component: ProfileComponent, canActivate: [profileGuard] },]; Combining Route GuardsWe covered what guards are and their usages but it is important to note that they are not mutually exclusives.You are absolutely free to use many of them and even of different sorts.Consider this example where we would like to grant access to the checkout page only if the user is authenticated and the basket is not empty ; we would also not want our user to accidentally leave the page if the payment is in progress:const routes: Route[] = [ { path: 'checkout', component: CheckoutComponent, canActivate: [authenticationGuard, basketNotEmptyGuard], canDeactivate: [paymentInProgressGuard] }];From a structural point of view, guards also cascade from the parent route to the child route.It means that guards defined in parents routes will also be evaluated for the child route.As a result, if you add a guard to ensure that the user is connected on the top of your routes hierarchy, all subsequent routes will be guarded by it. Inlined GuardsFinally, and thanks to the functional guards, you do not need to create a separate function every time you want to use a guard.Sometime the logic for a guard is very simple and does not need any additional file or declaration.As an example, preventing a user to leave the page can be as simple as that:const routes: Route[] = [ { path: 'sign-in', component: SignInComponent, canDeactivate: [() => !inject(SignInComponent).registrationForm.touched] }];Note that a drawback of this way of writing your guards is that you won't be able to unit test it! TakeawaysRoute guards play a crucial role in controlling and managing access to routes within your application.In this article, we explored the concept of route guards, their purpose, and how to select the appropriate guard based on specific use cases.We also learned how to enhance the user experience by leveraging guard hierarchies and implementing the necessary guards. By understanding and utilizing route guards effectively, you can ensure secure and controlled navigation throughout your application.I hope that you learnt something useful there! Pierre BouillonFollow Software Engineer | Privacy Advocate | Coffee Lover Photo by Flash Dantz on Unsplash



This post first appeared on VedVyas Articles, please read the originial post: here

Share the post

Demystifying Angular Route Guards: A Beginner's Guide to Secure Navigation

×

Subscribe to Vedvyas Articles

Get updates delivered right to your inbox!

Thank you for your subscription

×