Angular 16 Firebase CanActivate Route Guard Tutorial
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.
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!