Securing Access: Building a Firebase-powered Login Page with Email Authentication and Authentication Guard (Part 8) in Your Angular-15 Ionic-7 App

Welcome back to the eighth part of our series on building a mobile app with Angular 15 and Ionic 7. In this post, we will learn how to create a login page with Firebase authentication using an email provider and authentication guard.

Firebase is a platform that provides various services for web and mobile development, such as hosting, database, storage, analytics, and authentication. Authentication is the process of verifying the identity of a user who wants to access a protected resource, such as an app or a website.

Firebase authentication offers several methods to authenticate users, such as email and password, phone number, Google, Facebook, Twitter, and more. In this tutorial, we will use the email and password method, which allows users to sign up and sign in with their email address and a password of their choice.

We will also use an authentication guard, which is a service that checks if a user is logged in before allowing them to access certain pages of our app. This way, we can protect our app from unauthorized access and provide a better user experience. Let's get started!


I have used Google’s Firebase service for authentication on the application which will allow users to enter their e-mail and password to create an new account or login into an existing one. I have used the following Firebase official documentation to understand and implement the steps in the latest versions:

Add Firebase to your JavaScript project

First, I will create a firebase account using my existing Google account. Then I will add a new project to the firebase console by entering the project details. For the moment, I will keep the environment to be under testing and not under production. Figure 23 shows the main home page of firebase console where all projects are listed:

Figure 23: Firebase console with project listings

Next, I register the by adding a web app to the project which will give me the web app credentials such as the API key.

Once the app is created in Firebase, I will use the credentials under the firebaseConfig object in my project files to access the firebase authentication services. Next, I will install firebase using the command:

npm install firebase

Then I will add the official Angular library for Firebase from this documentation:

angular/angularfire: The official Angular library for Firebase. (github.com)

npm: @angular/fire

And install the above angular library for firebase using the command:

npm install @angular/fire

Next, I will enter the configuration from project settings to the environment.ts file in the following way:

export const environment = {
  production: false,
  ...
  //firebase API key
  firebase: {
    apiKey: 'MY-API-KEY',
    authDomain: 'grange-mobile-karan.firebaseapp.com',
    projectId: 'grange-mobile-karan',
    storageBucket: 'grange-mobile-karan.appspot.com',
    messagingSenderId: 'MY-SENDER-ID',
    appId: 'MY-APP-ID',
  },
...
};

When the packages are installed, the specific modules are generally automatically imported into the main.ts providers array, but if not, can be added manually in the following way with the link to firebase API from environment file:

...
//firebase imports for angular fire for authentication, firestore and storage
import { provideFirebaseApp, initializeApp } from '@angular/fire/app';
import { getFirestore, provideFirestore } from '@angular/fire/firestore';
import { getStorage, provideStorage } from '@angular/fire/storage';
import {
  FacebookAuthProvider,
  GoogleAuthProvider,
  TwitterAuthProvider,
  getAuth,
  provideAuth,
} from '@angular/fire/auth';

...

if (environment.production) {
  enableProdMode();
}

bootstrapApplication(AppComponent, {
  providers: [
    AuthGuard,
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
    importProvidersFrom(
...
      provideFirebaseApp(() => initializeApp(environment.firebase)),
      provideFirestore(() => getFirestore()),
      provideStorage(() => getStorage()),
      provideAuth(() => getAuth()),
      AuthGuard
    ),
    provideRouter(routes),
  ],
});

Next, I will create a service to access the firebase service to create the register and login services. I use the following command to create the authentication service:

ionic generate service services/auth

Now, in the auth.service.ts file, I will import the authentication service from angular fire and create the logic to register a new user and login to an existing one in the following way:

import { Injectable } from '@angular/core';
import {
  Auth, createUserWithEmailAndPassword, signInWithEmailAndPassword,signOut,
} from '@angular/fire/auth';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  constructor(private auth: Auth) {} //inject the auth service

  async register({ email, password }: { email: string; password: string }) {
    try {
      //creating a try catch block to catch any errors
      const user = await createUserWithEmailAndPassword(
        this.auth, email, password
      ); //if the user is registered, return the user
      return user;
    } catch (e) {
      return null; //if the user is not registered, return null
    }
  }

  // Binding element 'email' implicitly has an 'any' type.ts(7031). Resolve this error by adding a type annotation to the binding element.
  async login({ email, password }: { email: string; password: string }) {
    // creating a try catch block to catch any errors
    try {
      const user = await signInWithEmailAndPassword(this.auth, email, password);
      // if the user is logged in, return the user
      return user;
    } catch (e) {
      return null; // if the user is not logged in, return null
    }
  }

  async logout() {
    //sign out the user
    return await signOut(this.auth);
  }
}

The above code exports a class called AuthService that contains methods for registering, logging in, and logging out a user. The constructor of the class takes in an instance of the Auth service from Firebase Authentication, which is used to interact with the Firebase Authentication API.

The register method is an asynchronous function that takes an object with an email and password property. It uses the createUserWithEmailAndPassword method from Firebase Authentication to create a new user with the given email and password. If the user is successfully registered, the method returns the user object; otherwise, it returns null.

The login method is similar to the register method but takes an object with an email and password property as its parameter. It uses the signInWithEmailAndPassword method from Firebase Authentication to authenticate the user with the given email and password. If the user is successfully authenticated, the method returns the user object; otherwise, it returns null.

The logout method is an asynchronous function that signs out the current user using the signOut method from Firebase Authentication. It returns a promise that resolves when the sign-out process is complete.

Note that the email and password parameters in the register and login methods are explicitly typed as strings using the TypeScript syntax of : { email: string; password: string }. This is to prevent the TypeScript compiler from throwing an error related to implicit any types.

Next, I will use this service in a newly created login page which will also act as an authentication guard to the tab4 page which displays user profile.

ionic generate page pages/login

I could’ve also applied the page as authentication guard on the entire app which is what happens in real-world applications but for convenience of testing, I’ve add the login page only to the profile page.

First, I will shift the rout path of login page from app.route.ts to tabs.route.ts and then, I will use the authentication guard service to create a logic in the tabs.route.ts which will allow a user to enter the profile page only if the user is authenticated, in the following way:

import { Routes } from '@angular/router';
import { TabsPage } from './tabs.page';
import {
  redirectUnauthorizedTo,
  canActivate,
  redirectLoggedInTo,
} from '@angular/fire/auth-guard';

const redirectUnauthorizedToLogin = () =>
  redirectUnauthorizedTo(['/tabs/login']); //if not logged in, redirect to login page
const redirectLoggedInToHome = () => redirectLoggedInTo(['tabs/tab4']); //if logged in, redirect to tabs page

export const routes: Routes = [
  {
    path: 'tabs',
    component: TabsPage,
    children: [
...,
      {
        path: 'tab4',
        loadComponent: () =>
          import('../tab4/tab4.page').then((m) => m.Tab4Page),
        ...canActivate(redirectUnauthorizedToLogin),
      },
      {
        path: 'login',
        loadComponent: () =>
          import('../login/login.page').then((m) => m.LoginPage),
        ...canActivate(redirectLoggedInToHome),
      },
      {
        path: '',
        redirectTo: '/tabs/tab1',
        pathMatch: 'full',
      },
...
    ],
  },

  {
    path: '',
    redirectTo: '/tabs/tab1',
    pathMatch: 'full',
  },
];

In the above code, the TabsPage component has several child routes for multiple pages, each of which corresponds to a different tab in the application. Each child route is defined as an object that specifies the path to the route, the loadComponent function that lazily loads the component for the route, and a canActivate property that specifies the guard function to run before allowing access to the route. In this case, the redirectUnauthorizedToLogin function is used as the guard function for the 'tab4' route, which means that the user will be redirected to the login page if they are not logged in. Similarly, the redirectLoggedInToHome function is used as the guard function for the 'login' route, which means that the user will be redirected to the home page if they are already logged in. The redirectUnauthorizedTo and redirectLoggedInTo functions are imported from the @angular/fire/auth-guard module, which is used to guard access to routes based on a user's authentication status.

Once the guard is in place, whenever the user clicks on the tab4 i.e. profile page from bottom navigation, they will land on the login page if they are not authenticated previously.

Now, I will use the authentication service to create the logic for the login page in login.page.ts file in the following way:

import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { IonicModule, ToastController, isPlatform } from '@ionic/angular';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { LoadingController, AlertController } from '@ionic/angular';
import { Router } from '@angular/router';
import { AuthService } from 'src/app/services/auth.service';
import { ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-login',
  templateUrl: './login.page.html',
  styleUrls: ['./login.page.scss'],
  standalone: true,
  imports: [IonicModule, CommonModule, FormsModule, ReactiveFormsModule],
})
export class LoginPage implements OnInit {
  credentials!: FormGroup; // form group for the login form fields (email and password)

  constructor(
    private fb: FormBuilder,
    private loadController: LoadingController,
    private alertController: AlertController,
    private router: Router,
    private authService: AuthService,
    private toastCtrl: ToastController
  ) { }

  public get email() {
    return this.credentials.get('email');
  }

  public get password() {
    return this.credentials.get('password');
  }

  ngOnInit() {
    // create a form group for the login form
    this.credentials = this.fb.group({
      email: ['', [Validators.required, Validators.email]], // email field
      password: ['', [Validators.required, Validators.minLength(6)]], // password field
    });
  }

  async register() {
    // register the user
    const loading = await this.loadController.create({
      // create a loading controller
      message: 'Registering...',
      spinner: 'crescent', // spinner type
      showBackdrop: true,
    });
    await loading.present(); // present the loading controller

    const user = await this.authService.register(this.credentials.value); // register the user
    await loading.dismiss(); // dismiss the loading controller

    if (user) {
      // if the user is registered, navigate to the profile page
      this.router.navigateByUrl('/tabs/tab4', { replaceUrl: true });

      const toast = await this.toastCtrl.create({
        message: 'Registration Successful.',
        duration: 3000,
        position: 'bottom',
      });
      await toast.present();
    } else {
      // if the user is not registered, show an alert message to the user to try again
      const alert = await this.alertController.create({
        header: 'Sorry, Registration Failed',
        message:
          'Please try again. Please note that you can only register with a valid email address and password. If you have already registered, please login.',
        buttons: ['OK'],
      });
      await alert.present(); // present the alert message
    }
  }

  async login() {
    // login the user
    const loading = await this.loadController.create({
      // create a loading controller
      message: 'Logging in...',
      spinner: 'crescent', // spinner type
      showBackdrop: true,
    });
    await loading.present(); // present the loading controller

    const user = await this.authService.login(this.credentials.value); // login the user
    await loading.dismiss(); // dismiss the loading controller

    if (user) {
            this.router.navigateByUrl('/tabs/tab4', { replaceUrl: true });

      console.log(user);

      const toast = await this.toastCtrl.create({
        message: 'Login Successful. Welcome to Your Profile Page!',
        duration: 3000,
        position: 'bottom',
      });
      await toast.present();
    } else {
      // if the user is not logged in, show an alert message to the user to try again
      const alert = await this.alertController.create({
        header: 'Sorry, Login Failed',
        message:
          'Please try again. If you are not registered, please register first. Please note that you can only register with a valid email address and password. ',
        buttons: ['OK'],
      });
      await alert.present(); // present the alert message
    }
  }
}

The above code defines a class called LoginPage that implements the OnInit interface. It has a constructor that takes several Angular service dependencies as arguments. These dependencies are used in the register() and login() methods of the class.

It also has a credentials property that is of type FormGroup. This is a property that is used to store the values of the login form fields (email and password). The ngOnInit() method of the class creates this FormGroup using the FormBuilder service. This service is used to create complex forms in Angular.

The LoginPage class has two additional methods called register() and login(). These methods are used to register and login the user respectively. Both of these methods first create a loading spinner using the LoadingController service, which is used to show a spinner to the user while the registration or login process is ongoing.

After creating the loading spinner, the register() and login() methods call methods on the AuthService service to register or login the user respectively. If the user is successfully registered or logged in, the methods navigate the user to the profile page using the Router service. The methods also show a success toast message using the ToastController service.

If the registration or login process fails, the methods show an error message using the AlertController service. The error message informs the user that the registration or login process has failed and prompts them to try again.

I referred to the following link to build this authentication feature:

Get Started with Firebase Authentication on Websites

Next, I will use the login methods and display them on the template file which is login.page.html in the following way:

<ion-header [translucent]="false">
  <ion-toolbar>
    <ion-title>Login/Sign Up</ion-title>
    <ion-progress-bar [value]="progress"></ion-progress-bar>
    <ion-buttons slot="start">
      <ion-back-button defaultHref="/"></ion-back-button>
    </ion-buttons>
    <ion-buttons slot="end">
      <ion-menu-button menu="main-menu"></ion-menu-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true" class="ion-padding">
  <form [formGroup]="credentials" (ngSubmit)="login()">
    <ion-input
      class="ion-margin-top"
      fill="outline"
      label="E-Mail"
      label-placement="floating"
      required
      formControlName="email"
      type="email"
    ></ion-input>
    <ion-note
      color="danger"
      slot="error"
      *ngIf="email && (email.dirty || email.touched) && email.errors"
    >
      Email is Invalid
    </ion-note>

    <ion-input
      class="ion-margin-top"
      label-placement="floating"
      fill="outline"
      label="Password"
      required
      formControlName="password"
      type="password"
    ></ion-input>
    <ion-note
      color="danger"
      slot="error"
      *ngIf="password && (password.dirty || password.touched) && password.errors"
    >
      Password is Invalid. It needs to be 6 characters.
    </ion-note>

    <ion-button
      class="ion-margin-top"
      expand="full"
      shape="round"
      color="primary"
      type="submit"
      [disabled]="credentials.invalid"
      [strong]="true"
    >
      Login
    </ion-button>
    <ion-button
      fill="outline"
      class="ion-margin-top"
      expand="full"
      shape="round"
      color="primary"
      type="button"
      [strong]="true"
      (click)="register()"
    >
      Create Account
    </ion-button>
  </form>
</ion-content>

The above code uses a form that contains two input fields for the user to enter their email and password. The formGroup attribute is set to credentials, which refers to a FormGroup object defined in the previous login.page.ts TypeScript code. The input fields are decorated with several Ionic attributes, such as fill, label, label-placement, and type. There are also ion-note elements included that will display error messages if the user inputs an invalid email or password. The code also includes two Ionic buttons for the user to submit the form or create a new account. The disabled attribute is set to credentials.invalid, which prevents the user from submitting the form if the input fields are not valid.

The ngSubmit event binding is used as an inbuilt angular directive that calls the submit method from the login button that has the type submit by default.

In the above code, the *ngIf directive is used to conditionally render the <ion-note> element based on the status of the email form control. The condition inside the directive is evaluated as follows:

  • email: refers to the FormControl object named email.
  • email.dirty: a property of the FormControl object that indicates whether the control has been changed.
  • email.touched: another property of the FormControl object that indicates whether the control has been blurred (i.e., the user has interacted with it and then moved focus away from it).
  • email.errors: yet another property of the FormControl object that contains the validation errors associated with the control.

The condition inside the *ngIf directive is evaluated to true if all the following conditions are met:

  • email is truthy (i.e., it is not null, undefined, or false).
  • email.dirty is truthy (i.e., the control has been changed).
  • email.touched is truthy (i.e., the control has been blurred).
  • email.errors is truthy (i.e., there are validation errors associated with the control).

If the condition is true, the <ion-note> element is rendered. Otherwise, it is not rendered. In the above code, the <ion-note> element is used to display an error message when the email input is invalid, and it is shown only if the email input has been touched or dirty and has validation errors.

There were certain errors when implementing the above form input validation but ultimately got resolved when I added the AND operators and checked for all conditions simultaneously. The same logic applies to the password input field too. 

After creating the template file, figure 25, 26, 27, 28, 29, and 30 demonstrates the multiple use cases of the authentication along with figure 31 that demonstrates the user field updated in the firebase authentication. Ignore the social media logins for the moment because I will explain their implementation in the upcoming topics.

Figure 25: Login/Signup Page

Figure 26: Invalid Credentials


Figure 27: No existing user



Figure 28: Registration failed


Figure 29: Registration Success


Figure 30: Login Success


Figure 31: User authenticated and added to Users in Firebase console at the top.


I forgot to mention this before, but in the firebase console in authentication, under Sign-IN method, it is important to add Email/Password as an authentication provider and enable it when the app is registered in firebase otherwise the authentication doesn’t work.

Next, I will create a logout button within the side menu in the app.component.html like this:

<ion-footer>
      <ion-list lines="none">
        <ion-item class="ion-margin" (click)="logout()">
          <ion-label>Logout</ion-label>
          <ion-icon slot="end" name="log-out-outline"></ion-icon>
        </ion-item>
...
      </ion-list>
...
</ion-footer>

And create the method logout within the app.component.ts in the following way:

...

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.scss'],
  standalone: true,
  imports: [
    ...
  ],
})
export class AppComponent {
  public environmentInjector = inject(EnvironmentInjector);

  constructor(
    private loadController: LoadingController,
    private router: Router,
    private authService: AuthService,
    private toastCtrl: ToastController
  ) {}

  // for the logout button on the menu page
  async logout() {
    // logout the user
    const loading = await this.loadController.create({
      // create a loading controller
      message: 'Logging out...',
      spinner: 'crescent', // spinner type
      showBackdrop: true,
    });
    await loading.present(); // present the loading controller

    await this.authService.logout(); // logout the user
    await loading.dismiss(); // dismiss the loading controller
    this.router.navigateByUrl('/tabs/login', { replaceUrl: true }); // navigate to the login page
    const toast = await this.toastCtrl.create({
      message: 'You have been logged out',
      duration: 2000,
      position: 'bottom',
    });
    toast.present();
  }
}

The above code contains a logout method that is triggered when the user clicks on the logout button on the menu page. First, it creates a LoadingController with a message and a spinner type to indicate to the user that the application is logging out. It then presents the loading controller to the user using the present() method. Next, it calls the logout method of the AuthService to log out the user. After the user is logged out, the loading controller is dismissed using the dismiss() method. Then, the application navigates to the login page using the Router and the navigateByUrl() method. Finally, it creates a ToastController to display a message to the user that they have been logged out. The toast message is then presented to the user using the present() method.

And with this, I have set up firebase authentication in the application with appropriate authentication guards provided by firebase itself.

In this blog post, we have learned how to create a login page with firebase authentication using email provider and authentication guard in our Angular-15 Ionic-7 app. We have seen how to use AngularFireAuth service to sign up, sign in and sign out users, as well as how to protect our routes with canActivate method. We have also learned how to use Ionic components such as ion-input, ion-button and ion-alert to create a responsive and user-friendly interface.

We hope you have enjoyed this tutorial and found it useful. If you have any questions or feedback, please leave a comment below. You can also e-mail me to checkout out the source code of this project on GitHub. Thank you for reading and happy coding!

Popular Posts

Perform CRUD (Create, Read, Update, Delete) Operations using PHP, MySQL and MAMP : Part 4. My First Angular-15 Ionic-7 App

Visualize Your Data: Showcasing Interactive Charts for Numerical Data using Charts JS Library (Part 23) in Your Angular-15 Ionic-7 App

How to Build a Unit Converter for Your Baking Needs with HTML, CSS & Vanilla JavaScript