Protect Angular 14 Routes with canActivate Interface

Last updated on: by Digamber
In this tutorial, I am going to explain how you can protect Angular 12 routes with canActivate Guard for Firebase users. Route guard prevents unauthorized users to access the Angular app.

I created this small Angular Firebase demo app for showing you how you can follow the best practice for Angular app’s route security. CanActivate interface provides the best example for angular app’s URL security.

I will create a simple function, this function will return true if the user is logged in. If the user is not logged in then it will return false.

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 firebase @angular/fire

Put your firebase configurations in 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';
@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')!);
    return user !== 'null' ? true : false;
  }
  // 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 guards
import { AuthGuard } from './shared/auth.guard';
import { SecureInnerPagesGuard } from './shared/secure-inner-pages.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, SecureInnerPagesGuard],
  bootstrap: [AppComponent],
})
export class AppModule {}

Build Auth Guard in Angular

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

ng g guard shared/auth
ng g guard shared/secure-inner-pages

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 using 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 {
  CanActivate,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  Router,
} from '@angular/router';
import { AuthService } from '../shared/auth.service';
import { Observable } from 'rxjs';
@Injectable({
  providedIn: 'root',
})
export class AuthGuard implements CanActivate {
  constructor(public authService: AuthService, public router: Router) {}
  
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> | Promise<boolean> | boolean {
    if (this.authService.isLoggedIn !== true) {
      window.alert('Access Denied, Login is Required to Access This Page!');
      this.router.navigate(['sign-in']);
    }
    return true;
  }
}

We are creating this guard to prevent users to access some pages when the user is already logged in.

Update code in shared/secure-inner-pages.guard.ts file.

import { Injectable } from '@angular/core';
import {
  CanActivate,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  Router,
} from '@angular/router';
import { AuthService } from '../shared/auth.service';
import { Observable } from 'rxjs';
@Injectable({
  providedIn: 'root',
})
export class SecureInnerPagesGuard implements CanActivate {
  constructor(public authService: AuthService, public router: Router) {}
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> | Promise<boolean> | boolean {
    if (this.authService.isLoggedIn) {
      window.alert('Access denied!');
      this.router.navigate(['user-profile']);
    }
    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';
import { SecureInnerPagesGuard } from './shared/secure-inner-pages.guard';
// Include route guard in routes array
const routes: Routes = [
  { path: '', redirectTo: '/sign-in', pathMatch: 'full' },
  {
    path: 'sign-in',
    component: SignInComponent,
    canActivate: [SecureInnerPagesGuard],
  },
  {
    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

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

Protect Angular 14 Routes with canActivate Interface

Digamber

I am Digamber, a full-stack developer and fitness aficionado. I created this site to bestow my coding experience with newbie programmers. I love to write on JavaScript, ECMAScript, React, Angular, Vue, Laravel.