Angular 12 Firebase Authentication Example Tutorial

This tutorial walks you through on how to create a complete Angular 12 Firebase Authentication system from scratch. We will implement and use the Firebase auth platform in Angular application to build a robust Login and Signup system.

Our user authentication application covers the following functionalities:

  • Sign-in with Google
  • Sign-in with username/password
  • Register with username/password
  • Email verification
  • Forgot password
  • Guard Routes with canActivate
  • Prevent user to access sign in and sign up URL when a user is already logged in
  • Handle logged-in user state with Local Storage

Create Angular Authentication Project

Install Angular CLI in your development system.

npm install -g @angular/cli

Execute command to install a fresh Angular application.

ng new angular-firebase-authentication

Move to the project root:

cd angular-firebase-authentication

Install Bootstrap package in Angular.

npm install bootstrap

Add bootstrap CSS in angular.json.

"styles": [
            "node_modules/bootstrap/dist/css/bootstrap.min.css",
            "src/styles.css"
          ]

Add Firebase in Angular

Adding Firebase in Angular is easy, it can be done with a single command. However, you must create a Firebase project for making the interaction between server and client.

npm install firebase @angular/fire

AngularFire2 is a Firebase package and has been installed, now add the given below code in app.module.ts.

import { AngularFireModule } from "@angular/fire";
import { AngularFireAuthModule } from "@angular/fire/auth";
import { AngularFirestoreModule } from '@angular/fire/firestore';
import { environment } from '../environments/environment';


@NgModule({
  imports: [
    AngularFireModule.initializeApp(environment.firebase),
    AngularFireAuthModule,
    AngularFirestoreModule,
  ]
})

Create Angular Components

Generate following components to deal with User Registration, Signin, Forget Password and Email Verification:

ng g c components/dashboard
ng g c components/sign-in
ng g c components/sign-up
ng g c components/forgot-password
ng g c components/verify-email

Add Routing

Next, we create routes for angular authentication app in app-routing.module.ts file.

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

import { SignInComponent } from './components/sign-in/sign-in.component';
import { SignUpComponent } from './components/sign-up/sign-up.component';
import { DashboardComponent } from './components/dashboard/dashboard.component';
import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component';
import { VerifyEmailComponent } from './components/verify-email/verify-email.component';

const routes: Routes = [
  { path: '', redirectTo: '/sign-in', pathMatch: 'full' },
  { path: 'sign-in', component: SignInComponent },
  { path: 'sign-up', component: SignUpComponent },
  { path: 'dashboard', component: DashboardComponent },
  { path: 'forgot-password', component: ForgotPasswordComponent },
  { path: 'email-verification', component: VerifyEmailComponent }
];

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

export class AppRoutingModule { }

Thereafter, add router-outlet in app.component.html file.

<router-outlet></router-outlet>

Create Authentication Service

To create Angular Authentication System we need to create central service with Firebase API. Place following code in ng-auth.service.ts file.

import { Injectable, NgZone } from '@angular/core';
import { auth } from 'firebase/app';
import { AngularFireAuth } from "@angular/fire/auth";
import { AngularFirestore, AngularFirestoreDocument } from '@angular/fire/firestore';
import { Router } from "@angular/router";

export interface User {
    uid: string;
    email: string;
    displayName: string;
    photoURL: string;
    emailVerified: boolean;
 }

@Injectable({
  providedIn: 'root'
})

export class NgAuthService {
    userState: any;

    constructor(
      public afs: AngularFirestore,
      public afAuth: AngularFireAuth,
      public router: Router,
      public ngZone: NgZone
    ) {
      this.afAuth.authState.subscribe(user => {
        if (user) {
          this.userState = user;
          localStorage.setItem('user', JSON.stringify(this.userState));
          JSON.parse(localStorage.getItem('user'));
        } else {
          localStorage.setItem('user', null);
          JSON.parse(localStorage.getItem('user'));
        }
      })
    }
  
    SignIn(email, password) {
      return this.afAuth.signInWithEmailAndPassword(email, password)
        .then((result) => {
          this.ngZone.run(() => {
            this.router.navigate(['dashboard']);
          });
          this.SetUserData(result.user);
        }).catch((error) => {
          window.alert(error.message)
        })
    }
  
    SignUp(email, password) {
      return this.afAuth.createUserWithEmailAndPassword(email, password)
        .then((result) => {
          this.SendVerificationMail();
          this.SetUserData(result.user);
        }).catch((error) => {
          window.alert(error.message)
        })
    }

    SendVerificationMail() {
        return this.afAuth.currentUser.then(u => u.sendEmailVerification())
        .then(() => {
          this.router.navigate(['email-verification']);
        })
    }    
  
    ForgotPassword(passwordResetEmail) {
      return this.afAuth.sendPasswordResetEmail(passwordResetEmail)
      .then(() => {
        window.alert('Password reset email sent, check your inbox.');
      }).catch((error) => {
        window.alert(error)
      })
    }
  
    get isLoggedIn(): boolean {
      const user = JSON.parse(localStorage.getItem('user'));
      return (user !== null && user.emailVerified !== false) ? true : false;
    }
  
    GoogleAuth() {
      return this.AuthLogin(new auth.GoogleAuthProvider());
    }
  
    AuthLogin(provider) {
      return this.afAuth.signInWithPopup(provider)
      .then((result) => {
         this.ngZone.run(() => {
            this.router.navigate(['dashboard']);
          })
        this.SetUserData(result.user);
      }).catch((error) => {
        window.alert(error)
      })
    }
  
    SetUserData(user) {
      const userRef: AngularFirestoreDocument<any> = this.afs.doc(`users/${user.uid}`);
      const userState: User = {
        uid: user.uid,
        email: user.email,
        displayName: user.displayName,
        photoURL: user.photoURL,
        emailVerified: user.emailVerified
      }
      return userRef.set(userState, {
        merge: true
      })
    }
   
    SignOut() {
      return this.afAuth.signOut().then(() => {
        localStorage.removeItem('user');
        this.router.navigate(['sign-in']);
      })
    }  
}
  • User model contains the data that needs to be communicated with the server.
  • Import all the Firebase auth services and inject them in the constructor.
  • Define a variable userState which sustain the user authentication state.

Inject NgAuthService in main app module.

import { NgAuthService } from "./ng-auth.service";

@NgModule({
  providers: [NgAuthService],
})

Build Angular Login with Firebase

Use Angular NgAuthService service to create:

  • Firebase login with username and password
  • Firebase login with Gmail
  • Firebase Sign in with Facebook

Add code in sign-in.component.ts file.

import { Component, OnInit } from '@angular/core';
import { NgAuthService } from "../../ng-auth.service";

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

export class SignInComponent implements OnInit {

  constructor(
    public ngAuthService: NgAuthService
  ) { }

  ngOnInit() { }

}

Place code in sign-in.component.html.

<div class="displayTable">
  <div class="displayTableCell">

    <div class="authBlock">
      <h3>Signin</h3>
      <div class="formGroup">
        <input type="text" class="form-control" placeholder="Username" #userName required>
      </div>

      <div class="formGroup">
        <input type="password" class="form-control" placeholder="Password" #userPassword required>
      </div>

      <div class="formGroup">
        <input type="button" class="btn btn-primary btn-block" value="Sign in"
          (click)="ngAuthService.SignIn(userName.value, userPassword.value)">
      </div>

      <div class="formGroup">
        <button type="button" class="btn btn-danger btn-block" (click)="ngAuthService.GoogleAuth()">
          Log in with Google
        </button>
      </div>

      <div class="forgotPassword">
        <span routerLink="/forgot-password">Forgot Password?</span>
      </div>
    </div>

    <div class="redirectToLogin">
      <span>Not registered yet?<span class="redirect" routerLink="/sign-up"> Register</span></span>
    </div>

  </div>
</div>

Build Firebase User Registration

Add code in sign-up.component.ts to create user registration with Firebase.

import { Component, OnInit } from '@angular/core';
import { NgAuthService } from "../../ng-auth.service";

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

export class SignUpComponent implements OnInit {

  constructor(public ngAuthService: NgAuthService) { }

  ngOnInit(): void {
  }

}

Place code in sign-up.component.html.

<div class="displayTable">
  <div class="displayTableCell">

    <div class="authBlock">
      <h3>Register User</h3>

      <div class="formGroup">
        <input type="email" class="form-control" placeholder="Email Address" #userEmail required>
      </div>

      <div class="formGroup">
        <input type="password" class="form-control" placeholder="Password" #userPwd required>
      </div>

      <div class="formGroup">
        <input type="button" class="btn btn-block btn-primary" value="Sign Up"
          (click)="ngAuthService.SignUp(userEmail.value, userPwd.value)">
      </div>

      <div class="formGroup">
        <button type="button" class="btn btn-block btn-danger" (click)="ngAuthService.GoogleAuth()">
          Continue with Google
        </button>
      </div>
    </div>

    <div class="redirectToLogin">
      <span>Already have an account? <span class="redirect" routerLink="/sign-in">Log In</span></span>
    </div>
  </div>

</div>

Build Forgot Password in Angular Firebase

Place code in forgot-password.component.ts to create Forgot Password in Angular 12 Firebase.

import { Component, OnInit } from '@angular/core';
import { NgAuthService } from "../../ng-auth.service";

@Component({
  selector: 'app-forgot-password',
  templateUrl: './forgot-password.component.html',
  styleUrls: ['./forgot-password.component.css']
})
export class ForgotPasswordComponent implements OnInit {

  constructor(public ngAuthService: NgAuthService) { }

  ngOnInit(): void {
  }

}

Paste code in forgot-password.component.html.

<div class="displayTable">
  <div class="displayTableCell">

    <div class="authBlock">
      <h3>Forgot Password</h3>

      <div class="formGroup">
        <input type="email" class="form-control" placeholder="Email Address" #passwordResetEmail required>
      </div>

      <div class="formGroup">
        <input type="submit" class="btn btn-primary btn-block" value="Reset Password"
          (click)="ngAuthService.ForgotPassword(passwordResetEmail.value)">
      </div>
    </div>

  </div>
</div>

Send Verification Email

Incorporate code in verify-email.component.ts to build sending verification email to newly registered user with Firebase and Angular.

import { Component, OnInit } from '@angular/core';
import { NgAuthService } from "../../ng-auth.service";

@Component({
  selector: 'app-verify-email',
  templateUrl: './verify-email.component.html',
  styleUrls: ['./verify-email.component.css']
})

export class VerifyEmailComponent implements OnInit {

  constructor(public ngAuthService: NgAuthService) { }

  ngOnInit(): void {
  }

}

Add code inside verify-email.component.html.

<div class="displayTable">
  <div class="displayTableCell">

    <div class="authBlock">
      <h3>Thank You for Signing up</h3>

      <div class="formGroup" *ngIf="ngAuthService.userState as user">
        <p class="text-center">Confirmation mail has been sent to <strong>{{user.email}}</strong>.</p>
      </div>

      <div class="formGroup">
        <button type="button" class="btn btn-block btn-success" (click)="ngAuthService.SendVerificationMail()">
          Resend Verification Email
        </button>
      </div>
    </div>


  </div>
</div>

Protect Routes with Angular 12 Route Guard

To secure routes in Angular, we need to use the CanActivate interface.

Interface that a class can implement to be a guard deciding if a route can be activated. If all guards return true, navigation continues. If any guard returns false, navigation is cancelled.

Create route guard service with following command.

ng generate guard auth

Add code in auth.guard.ts.

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { NgAuthService } from "./ng-auth.service";

@Injectable({
  providedIn: 'root'
})

export class AuthGuard implements CanActivate {

  constructor(
    public ngAuthService: NgAuthService,
    public router: Router
  ){ }

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
      if(this.ngAuthService.isLoggedIn !== true) {
        this.router.navigate(['sign-in'])
      }
      return true;
  }
  
}

Register route guard with component, add code in app-routing.module.ts.

import { AuthGuard } from "./auth.guard";

const routes: Routes = [
  { path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] },
];

Store User State with Local Storage

In this step we will learn to keep Logged-in user state with localStorage object in Angular 12.

Add code in ng-auth.service.ts.

import { Injectable, NgZone } from '@angular/core';
import { auth } from 'firebase/app';
import { AngularFireAuth } from "@angular/fire/auth";
import { AngularFirestore, AngularFirestoreDocument } from '@angular/fire/firestore';
import { Router } from "@angular/router";

export interface User {
    uid: string;
    email: string;
    displayName: string;
    photoURL: string;
    emailVerified: boolean;
 }

@Injectable({
  providedIn: 'root'
})

export class NgAuthService {
    userState: any;

    constructor(
      public afs: AngularFirestore,
      public afAuth: AngularFireAuth,
      public router: Router,
      public ngZone: NgZone
    ) {
      this.afAuth.authState.subscribe(user => {
        if (user) {
          this.userState = user;
          localStorage.setItem('user', JSON.stringify(this.userState));
          JSON.parse(localStorage.getItem('user'));
        } else {
          localStorage.setItem('user', null);
          JSON.parse(localStorage.getItem('user'));
        }
      })
    }
  
    SignIn(email, password) {
      return this.afAuth.signInWithEmailAndPassword(email, password)
        .then((result) => {
          this.ngZone.run(() => {
            this.router.navigate(['dashboard']);
          });
          this.SetUserData(result.user);
        }).catch((error) => {
          window.alert(error.message)
        })
    }
  
    SignUp(email, password) {
      return this.afAuth.createUserWithEmailAndPassword(email, password)
        .then((result) => {
          this.SendVerificationMail();
          this.SetUserData(result.user);
        }).catch((error) => {
          window.alert(error.message)
        })
    }

    SendVerificationMail() {
        return this.afAuth.currentUser.then(u => u.sendEmailVerification())
        .then(() => {
          this.router.navigate(['email-verification']);
        })
    }    
  
    ForgotPassword(passwordResetEmail) {
      return this.afAuth.sendPasswordResetEmail(passwordResetEmail)
      .then(() => {
        window.alert('Password reset email sent, check your inbox.');
      }).catch((error) => {
        window.alert(error)
      })
    }
  
    get isLoggedIn(): boolean {
      const user = JSON.parse(localStorage.getItem('user'));
      return (user !== null && user.emailVerified !== false) ? true : false;
    }
  
    GoogleAuth() {
      return this.AuthLogin(new auth.GoogleAuthProvider());
    }
  
    AuthLogin(provider) {
      return this.afAuth.signInWithPopup(provider)
      .then((result) => {
         this.ngZone.run(() => {
            this.router.navigate(['dashboard']);
          })
        this.SetUserData(result.user);
      }).catch((error) => {
        window.alert(error)
      })
    }
  
    SetUserData(user) {
      const userRef: AngularFirestoreDocument<any> = this.afs.doc(`users/${user.uid}`);
      const userState: User = {
        uid: user.uid,
        email: user.email,
        displayName: user.displayName,
        photoURL: user.photoURL,
        emailVerified: user.emailVerified
      }
      return userRef.set(userState, {
        merge: true
      })
    }
   
    SignOut() {
      return this.afAuth.signOut().then(() => {
        localStorage.removeItem('user');
        this.router.navigate(['sign-in']);
      })
    }  
}

Place code in dashboard.component.html.

 <div class="container mt-5" style="max-width: 600px">

   <div class="row" *ngIf="ngAuthService.userState as user">
     <div class="media">
       <img class="align-self-start mr-5 img-thumbnail rounded-circle"
         src="{{(user.photoURL) ? user.photoURL : '/assets/dummy.jpg'}}" alt="{{user.displayName}}">
       <div class="media-body">
         <h2>Hi: <strong>{{(user.displayName) ? user.displayName : 'User'}}</strong></h2>
         <p>User ID: <strong>{{user.uid}}</strong></p>
         <p>Email: <strong>{{user.email}}</strong></p>
       </div>
     </div>
   </div>

   <a class="btn btn-block btn-primary" (click)="ngAuthService.SignOut()">
     Sign Out
   </a>

 </div>

To start the application execute ng serve --open command.

You can download the full code of this example app from GitHub.