Angular 16 Firebase CanActivate Route Guard Tutorial

Last Updated on by in Angular
In this tutorial, I am going to explain how you can create route guard using CanActivate API in Angular Firebase authentication app.Route guard prevents unauthorized users to access the important URLs in Angular app. To protect Angular Firebase 16 routes we will use canActivate Guard.

In Angular, the canActivate interface is part of the Angular Router and is used to implement route guards.

Route guards allow you to control whether a user is allowed to navigate to or leave a certain route.

The canActivate interface specifically is used to determine if a user can activate a route, meaning if they are allowed to navigate to that route.

I created this small Angular Firebase demo app for showing you how you can follow the best practice for Angular app’s route security.

I will create a simple function using canActivate, this function will return true if the user is logged in else it will return false.

Create Angular Project

In order to create this demo app you must have Node JS development environment set up in your machine.

Angular projects are created using Angular CLI. Run the below command to download the Angular CLI, tool.

npm install @angular/cli -g

It’s time to setup Angular project, execute command to install the framework.

ng new angular-demo

It will ask you the following questions…

Would you like to add Angular routing?
Select y and Hit Enter.

Which stylesheet format would you like to use? (Use arrow keys)
Choose SCSS and hit Enter

Head over to tsconfig.json file, under the angularCompilerOptions set:

"angularCompilerOptions": {
    "skipLibCheck": true
  }

Set Up Firebase in Angular

We assume, you have already created the Firebase app, make sure to install Firebase package in Angular application.

npm install @angular/fire firebase@9.16.0 --legacy-peer-deps

First, create src/environments/ folder and environment.ts file, then add firebase configurations in environments/environment.ts file.

export const environment = {
  production: false,
  firebase: {
    apiKey: "xxxxxxxx-xxxxxxxx",
    authDomain: "xxxxxxxxxxxxxxxxxxxxxxxx",
    projectId: "xxxxxxxx",
    storageBucket: "xxxxxxxx",
    messagingSenderId: "xxxxxx",
    appId: "xxxxx",
  }
};

In the subsequent, we will create route guards, services at the same time we will also show you how to import significant firebase modules in AppModule class.

Create Auth Service

Execute command to generate auth.service.ts file to store core logic for our app.

ng g service shared/auth

In auth.service.ts file, we have mentioned the following methods.

import { Injectable, NgZone } from '@angular/core';
import * as auth from 'firebase/auth';
import { AngularFireAuth } from '@angular/fire/compat/auth';
import { Router } from '@angular/router';
import { of, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  userData: any;

  constructor(
    public afAuth: AngularFireAuth,
    public router: Router,
    public ngZone: NgZone // NgZone service to remove outside scope warning
  ) {
    // Setting logged in user in localstorage else null
    this.afAuth.authState.subscribe((user) => {
      if (user) {
        this.userData = user;
        localStorage.setItem('user', JSON.stringify(this.userData));
        JSON.parse(localStorage.getItem('user')!);
      } else {
        localStorage.setItem('user', 'null');
        JSON.parse(localStorage.getItem('user')!);
      }
    });
  }

  // Returns true when user is looged in and email is verified
  get isLoggedIn(): boolean {
    const user = JSON.parse(localStorage.getItem('user')!);
    if (user == null) {
      return false;
    } else {
      return true;
    }
  }

  // Sign in with Google
  GoogleAuth() {
    return this.AuthLogin(new auth.GoogleAuthProvider());
  }

  // Auth logic to run auth providers
  AuthLogin(provider: any) {
    return this.afAuth
      .signInWithPopup(provider)
      .then((result: any) => {
        this.ngZone.run(() => {
          this.router.navigate(['user-profile']);
        });
      })
      .catch((error: any) => {
        window.alert(error);
      });
  }

  // Sign out
  SignOut() {
    return this.afAuth.signOut().then(() => {
      localStorage.removeItem('user');
      this.router.navigate(['sign-in']);
    });
  }
}
  • Save Firebase user in localStorage
  • isLoggedIn() getter method checks whether the Firebase user is logged in or not
  • GoogleAuth() method for sign in with Google
  • SignOut() method for sign out from app Angular Firebase app

Now, you are ready to import route guards, services, and firebase packages in app.module.ts file.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';

// Firebase services + enviorment module
import { AngularFireModule } from '@angular/fire/compat';
import { AngularFireAuthModule } from '@angular/fire/compat/auth';
import { AngularFireStorageModule } from '@angular/fire/compat/storage';
import { AngularFirestoreModule } from '@angular/fire/compat/firestore';
import { AngularFireDatabaseModule } from '@angular/fire/compat/database';
import { environment } from '../environments/environment';

// Auth service
import { AppRoutingModule } from './app-routing.module';
import { AuthService } from './shared/auth.service';

// Import canActivate guard
import { AuthGuard } from './shared/auth.guard';
import { UserProfileComponent } from './components/user-profile/user-profile.component';
import { SignInComponent } from './components/sign-in/sign-in.component';

@NgModule({
  declarations: [AppComponent, UserProfileComponent, SignInComponent],
  imports: [
    BrowserModule,
    AngularFireModule.initializeApp(environment.firebase),
    AngularFireAuthModule,
    AngularFirestoreModule,
    AngularFireStorageModule,
    AngularFireDatabaseModule,
    AppRoutingModule,
  ],
  providers: [AuthService, AuthGuard],
  bootstrap: [AppComponent],
})

export class AppModule {}

Create CanActivate Auth Guard in Angular

Below commands will order Angular CLI to generate canActivate route guard files.

ng g guard shared/auth

Following message popup on your terminal screen.

? Which interfaces would you like to implement?

You have to select CanActivate guard from the option list.

I am going to write logic in AuthGuard class using canActivate interface method to prevent unauthorized user access.

I’ll be importing isLoggedIn getter method from auth.service.ts service module.

This getter method will return true if the user is present in localStorage else return false if the user is null in localStorage.

CanActivate method works on the boolean result, if the user is not logged in this guard will block the unauthorized access and redirect the user to sign in page.

Otherwise, it will allow the user to access the page.

Update code in shared/auth.guard.ts file.

import { Injectable } from '@angular/core';
import {
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  Router,
  UrlTree,
} from '@angular/router';
import { AuthService } from '../shared/auth.service';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class AuthGuard {
  constructor(public authService: AuthService, public router: Router) {}

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> | Promise<boolean> | UrlTree | boolean {
    if (this.authService.isLoggedIn !== true) {
      window.alert('Access Denied, Login is Required to Access This Page!');
      this.router.navigate(['sign-in']);
    }
    return true;
  }
}

Using AuthGuard in Angular

In this step, you have to create the app-routing.module.ts.

Below code example shows how to use auth guards in Angular routing file hence open and add the code in routing file.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

// Required components for which route services to be activated
import { SignInComponent } from './components/sign-in/sign-in.component';
import { UserProfileComponent } from './components/user-profile/user-profile.component';

// Import canActivate guards
import { AuthGuard } from './shared/auth.guard';

// Include route guard in routes array
const routes: Routes = [
  { path: '', redirectTo: '/sign-in', pathMatch: 'full' },
  {
    path: 'sign-in',
    component: SignInComponent,
  },
  {
    path: 'user-profile',
    component: UserProfileComponent,
    canActivate: [AuthGuard],
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})

export class AppRoutingModule {}

Create Components

You can use the below command to generate the components:

ng g c components/sign-in
ng g c components/user-profile

Next, update given code in sign-in.component.html file:

<nav class="navbar navbar-dark fixed-top bg-dark flex-md-nowrap p-0 shadow">
  <ul class="navbar-nav px-3">
    <li class="nav-item text-nowrap">
      <a class="nav-link" routerLink="/user-profile">
        <i class="fas fa-user"></i>
        User Profile
      </a>
    </li>
  </ul>
</nav>

<div class="displayTable">
  <div class="displayTableCell">
    <div class="authBlock">
      <h3>Sign in</h3>
      <div class="formGroup">
        <button
          type="button"
          class="btn googleBtn"
          (click)="authService.GoogleAuth()"
        >
          <i class="fab fa-google-plus-g"></i>
          Continue with Google
        </button>
      </div>
    </div>
  </div>
</div>

Now, update given code in sign-in.component.ts file:

import { Component, OnInit } from '@angular/core';
import { AuthService } from '../../shared/auth.service';

@Component({
  selector: 'app-sign-in',
  templateUrl: './sign-in.component.html',
  styleUrls: ['./sign-in.component.scss'],
})

export class SignInComponent implements OnInit {
  constructor(public authService: AuthService) {}
  ngOnInit() {}
}

Next, update given code in user-profile.component.html file:

<nav class="navbar navbar-dark fixed-top bg-dark flex-md-nowrap p-0 shadow">
  <span class="navbar-brand col-sm-3 col-md-2 mr-0"> Dashboard </span>
  <ul class="navbar-nav px-3">
    <li class="nav-item text-nowrap">
      <a class="nav-link" routerLink="/sign-in">
        <i class="fas fa-sign-in-alt"></i>
        Sign in
      </a>
    </li>
  </ul>
</nav>

<!-- Sidebar navigation -->
<div class="container-fluid dashboardContainer">
  <div class="row">
    <nav class="col-md-2 d-md-block bg-light sidebar">
      <div class="sidebar-sticky">
        <ul class="nav flex-column">
          <li class="nav-item">
            <a class="nav-link active"> User Profile </a>
          </li>
          <!-- Calling SignOut() Api from AuthService -->
          <li class="nav-item">
            <a class="nav-link" (click)="authService.SignOut()"> Log out </a>
          </li>
        </ul>
      </div>
    </nav>

    <!-- Main content -->
    <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-4">
      <div class="inner-adjust">
        <div class="pt-3 pb-2 mb-3 border-bottom">
          <h1 class="h2">User Profile</h1>
        </div>
        <!-- Show user data when logged in -->
        <div class="row" *ngIf="authService.userData as user">
          <div class="col-md-12">
            <div class="media">
              <img
                class="align-self-start mr-5 img-thumbnail rounded-circle"
                src="{{
                  user.photoURL ? user.photoURL : '/assets/dummy-user.png'
                }}"
                alt="{{ user.displayName }}"
              />
              <div class="media-body">
                <h1>
                  Hello:
                  <strong>{{
                    user.displayName ? user.displayName : "Super Admin"
                  }}</strong>
                </h1>
                <p>
                  User ID: <strong>{{ user.uid }}</strong>
                </p>
                <p>
                  Email: <strong>{{ user.email }}</strong>
                </p>
              </div>
            </div>
          </div>
        </div>
      </div>
    </main>
  </div>
</div>

Now, update given code in user-profile.component.ts file:

import { Component, OnInit } from '@angular/core';
import { AuthService } from '../../shared/auth.service';

@Component({
  selector: 'app-user-profile',
  templateUrl: './user-profile.component.html',
  styleUrls: ['./user-profile.component.scss'],
})

export class UserProfileComponent implements OnInit {

  constructor(public authService: AuthService) {}

  ngOnInit() {}
  
}

Open the terminal window, next type the given command and finally hit enter to run and test the app.

ng serve --open

Access the app using the below URL:

http://localhost:4200/sign-in

You can now see if you try to access the url without signing in you will be shown access denied message.

Protect Angular Routes with canActivate Interface

You can download the full code of this guide from GitHub.

Don’t forget to give my repo a star. The Angular Firebase Route Guard example tutorial is over!