Protect Angular 13 Routes with canActivate Interface
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.