Angular Authentication: Securing Routes with Route Guards

Jacob Neterer
4 min readMay 2, 2020

In your Angular application, you probably have pages you only want authenticated users to access, but how can you control this? Angular route guards make this integration quick and easy.

Where to store your route guard

Before you create your first route guard, where should you store it in your application code? In your Angular application you might have an /auth directory that contains authentication-related files. The route guard will work in conjunction with an auth service that contains an HTTP request to your server (or a serverless SDK request) that determines the user’s authenticated state. The directory structure could look something like this:

src
|_ app
|_ ...
|_ auth
|_ auth.guard.ts
|_ auth.service.ts

Creating the route guard

You can make use of the Angular CLI to create your route guard.

Generate the route guard

In the root of your application directory, you can generate your route guard like so:

ng generate guard auth/auth

If the CLI asks you which interface you would like to implement, choose CanActivate. You will see that the CLI will create the /auth directory for you if you haven’t created it yourself. The auth.guard.ts file should look like this:

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {

canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return true;
}
}

CanActivate is the interface that determines if the user can proceed to a specific route. I’ve edited the return type to be just Observable<boolean>. Your application might be different.

Determining the user’s authenticated state

Creating an isAuthenticated function

In your auth.service.ts file, you should have a function that determines the authenticated state of the user. This function might call an API endpoint or a serverless SDK function. In this example we will assume you are calling an API endpoint. Here is an example of what your auth.service.ts file might look like:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AuthService {
constructor(private http: HttpClient) { } isAuthenticated() {
return this.http.get('/auth/isAuthenticated');
}
}

We will be using the isAuthenticated() function to call an API endpoint /auth/isAuthenticated that will return an object indicating the authenticated status: { authenticated: (true | false) }.

Using isAuthenticated() in your route guard

To use this function in your route guard, you need to import it and define it in your constructor. You also need to import Router to redirect the user to the login page if they are not authenticated. In your auth.guard.ts file:

...
import { AuthService } from './auth.guard';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
... constructor(private authService: AuthService,
private router: Router) { }

Next, lets edit your CanActivate function to use the isAuthenticated() function:

...
import { Observable, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
... canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.authService.isAuthenticated().pipe(map((response: { authenticated: boolean}) => {
if (response.authenticated) {
return true;
}
this.router.navigate(['/login']);
return false;
}), catchError((error) => {
this.router.navigate(['/login']);
return of(false);
}));
}
}

In the above code, we are calling the isAuthenticated() function. We make use of the pipe and map operator to access the response. We then use the response to determine the user’s authenticated state. If the user is authenticated, we return true. Otherwise we use the Router to redirect the user to the login page and return false.

We are also using the catchError operator to catch any errors from the api. If there is an error, we will redirect the user to the homepage and return false. Did you notice that false is wrapped in an of rxjs operator? This is because catchError requires you to return an observable. Of takes a value and wraps that value in an observable. You can read more about rxjs here.

Add your guard to your routes

In your routing modules, app-routing.module.ts, you need to add your guard to all the routes you want to protect. In this example, lets protect the user’s profile page. Add the canActivate property to your profile route that takes an array of guards with only your AuthGuard. Also add the AuthGuard to the providers array.

...
import { AuthGuard } './auth/auth.guard';
const routes: Routes = [
...
{
path: 'profile',
component: ProfileComponent,
canActivate: [ AuthGuard ]
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: [ AuthGuard ]
})
export class AppRoutingModule { }

Adding your guard to the route’s canActivate property ensures that this guard will be run anytime someone tries to access the profile page, only allowing them to proceed if they are authenticated. Additionally, without adding it to the providers array, your guard will not be registered and the application won’t run. Don’t forget to add any guards you may have in your application to this array.

Final Thoughts

Route guards ensure that your protected pages can only be accessed behind user authentication. Please note that you should also validate that your other API endpoints that provide access to user data for these protected pages also has user authentication validation. It is not enough to only validate user authentication state in the front end. Otherwise, people can steal these endpoints and access data without any form of validation.

Stay tuned for related posts on retrieving page data using route resolvers and creating route guards for protecting pages based on user roles! Thanks for reading!

You can find me on Twitter, GitHub, LinkedIn, and my website!

--

--

Jacob Neterer

Lead software engineer specializing in web and mobile development. Always learning new things. “There is no growth in the comfort zone…”