Adding a login page to an Ionic Tabs project

September 19, 2017, 10:00 am Categories:

Categories

One of the most common questions I receive from readers of this site is how to add a login screen to a Tabs project layout so that the tabs are only visible AFTER a user has successfully logged in.

In this tutorial I'll show you how to accomplish that while also integrating a simple Firebase authentication service into the demonstration app we'll be building.

What to expect

By the end of the tutorial you should be able to launch the application in your desktop browser and see the following login form:

Ionic login screen to access Firebase service

Upon successful authentication with the Firebase service you will then be redirected to the application's tabbed layout like so:

Ionic login screen to access Firebase service

We won't be fleshing out the logged-in part of the application with any functionality as the sole purpose of this tutorial is to demonstrate how to integrate a log-in mechanism with a Tabs based layout.

Notice the logout option at the bottom right hand side of the screen? We'll use this to redirect the application back to the login page - using a combination of Firebase and Ionic's NavController API.

Ready to get started?

First things first

Before we begin you'll need to you have your own Firebase account ready for use and ensure that your system environment has the necessary software installed (I.e. Node, Ionic CLI etc) and configured for development/testing.

Assuming these are in place the project we'll be creating will make use of the following:

  • Firebase authentication
  • Angular FormBuilder API
  • Angular Services

The completed application will then be deployed within the desktop browser of your choice (I.e. Chrome, Firefox, Safari etc) so there won't be any need for generating iOS and/or Android build packages.

Setting up Firebase

Navigate to the Firebase website in your browser and, if already registered with the service, log in to your account (if you're not registered then go ahead and create an account, it's free!)

Once logged in you should see all of your existing projects (if you have any) displayed on your account home page like so:

Firebase projects landing page

Let's begin creating a brand new project by selecting the CREATE NEW PROJECT button and, in the modal window that appears, entering your project name (name this whatever you feel works best) and selecting the Country/region from the drop down menu:

Firebase create project

Once your project has been created you can choose how to add Firebase to your application by simply choosing from the following options:

  • Add Firebase to your iOS app
  • Add Firebase to your Android app
  • Add Firebase to your web app

Go ahead and select the Add Firebase to your web app option as this will provide us with the necessary configuration values to integrate Firebase into our Ionic application regardless of platform (I.e web, iOS or Android):

Choose how Firebase will be added to your app

From the modal window that appears, copy the following configuration keys and values:

  • apiKey
  • authDomain
  • databaseURL
  • storageBucket
  • messagingSenderId

Simply copy but do NOT edit these values as you WON'T be able to connect with your Firebase app without them:

Our completed Firebase database

Once copied paste these configuration keys/values into a text file for safekeeping (we'll subsequently enter these into an environment.ts file once we've created our Ionic project from the command line).

Next we need to configure the authentication options for the project.

From the left-hand menu in the Firebase console select Authentication.

Click onto the SIGN-IN METHOD tab on the Authentication screen and, from the listed Sign-in providers, select the E-mail/Password option:

Firebase authentication options

Enable this authentication option:

Enabling e-mail authentication for Firebase

Once saved select the USERS tab, click onto the ADD USER button and enter your e-mail/password for this project:

Adding an email account for Firebase authentication

And that's all we'll need as far as Firebase configuration is concerned!

With the Firebase aspect of our project in place we can now move onto the next step in the tutorial - creating our Ionic project.

Setting up the project codebase

Open up your command line software, navigate to where you store digital projects on your system, and issue the following instruction to create a new Ionic project - imaginatively titled tabs-demo- using the tabs template option:

ionic start tabs-demo tabs

Once completed, change into the project and generate the following resources:

ionic g page login
ionic g provider auth

Followed by installing the necessary node packages that will allow us to make use of the Firebase Web API (and also avoid any polyfill errors that might be thrown up during application build processes):

npm install --save firebase
npm install --save promise-polyfill

Next we need to make some amendments to the application's root module - src/app/app.module.ts - so that the HttpModule is declared in the imports section (WHY this isn't automatically declared when a provider is generated through the Ionic CLI I don't know but, currently, if the HttpModule isn't imported and declared an error is thrown during build processes).

With said amendment in place your application's root module should resemble the following:

import { NgModule, ErrorHandler } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpModule } from '@angular/http';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';

import { AboutPage } from '../pages/about/about';
import { ContactPage } from '../pages/contact/contact';
import { HomePage } from '../pages/home/home';
import { TabsPage } from '../pages/tabs/tabs';
import { LoginPage } from '../pages/login/login';

import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { AuthProvider } from '../providers/auth/auth';

@NgModule({
  declarations: [
    MyApp,
    AboutPage,
    ContactPage,
    HomePage,
    TabsPage,
    LoginPage
  ],
  imports: [
    BrowserModule,
    HttpModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    AboutPage,
    ContactPage,
    HomePage,
    TabsPage,
    LoginPage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    AuthProvider
  ]
})
export class AppModule {}

Next you'll want to create a new folder titled environments in your tabs-demo/src directory that contains a single typescript file - environment.ts - with your Firebase Web API configuration keys/values like so:

export const environment = {
   production: false,
   firebase : {
      apiKey 				: "PASTE_YOUR_FIREBASE_API_KEY_HERE",
      authDomain			: "PASTE_YOUR_FIREBASE_DOMAIN_HERE",
      databaseURL			: "PASTE_YOUR_FIREBASE_DATABASE_URL_HERE",
      projectId				: "PASTE_YOUR_FIREBASE_PROJECT_ID_HERE",
      storageBucket			: "PASTE_YOUR_FIREBASE_STORAGE_BUCKET_HERE",
      messagingSenderId		: "PASTE_YOUR_FIREBASE_MESSAGING_SENDER_ID_HERE"
   }
};

Once completed double check that this file resides within the following location: tabs-demo/src/environments/environment.ts.

Finally you'll need to amend the root component for the project - tabs-demo/src/app/app.component.ts - so that the following changes are made:

  • The firebase node package is imported along with the environment.ts file
  • The LoginPage component is imported
  • The rootPage value is changed from TabsPage to LoginPage
  • Firebase is able to be connected to with credentials supplied through the environment.ts file

These changes should result in a root component file that looks like this:

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import firebase from 'firebase';
import { environment } from '../environments/environment';

import { LoginPage } from '../pages/login/login';

@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  rootPage:any = LoginPage;

  constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) {
    platform.ready().then(() => {
      // Okay, so the platform is ready and our plugins are available.
      // Here you can do any higher level native things you might need.
      statusBar.styleDefault();
      splashScreen.hide();
    });

    firebase.initializeApp(environment.firebase);
  }
}

With the foundational elements for our project now in place let's move onto configuring the necessary methods for the Firebase authentication service.

Defining project authentication

In the Setting up Firebase section we defined the sign-in provider for use with this project - which involves supplying an e-mail/password value for our account.

In order to authenticate access for our project we'll make use of a small subset of the auth() service methods supplied by the Firebase Web API.

These will be crafted into our own custom authentication methods that we'll shortly write within the tabs-demo/src/providers/auth/auth.ts service.

By placing these within a service we ensure that our authentication logic will be delivered from one file and not scattered/repeated over multiple components - which makes for sensible and effective code management (particularly if the project should grow/be accessed by other developers).

The Auth service is fairly minimal (as we're only using a small subset of the available methods supplied by the Firebase auth() class) and simply handles the following requirements:

  • Authenticates the user with the signInWithEmailAndPassword method
  • Logs the user out from Firebase

Can't get much simpler than that - as demonstrated within the tabs-demo/src/providers/auth/auth.ts service:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Observable';

// DON'T forget to import the Firebase node package!
import * as firebase from 'firebase';



@Injectable()
export class AuthProvider {

   public user                   : Observable;

 
   constructor(public http       : Http) 
   {
      firebase.auth().onAuthStateChanged((user) =>
      {
        if (user) 
        {
          // User is signed in.
          console.log('User is signed in');
        } 
        else 
        {
          // No user is signed in.
          console.log('User is NOT signed in');
        }
      });
   }




   /** 
    * Use Firebase Web API signInWithEmailAndPassword method 
    * to authenticate user login attempt
    * 
    * @method loginWithEmailAndPassword
    * @param email    {string}      User e-mail address (gmail)
    * @param password {string}      Gmail address password
    * @return {Promise}
    */
   loginWithEmailAndPassword(email     : string, 
                             password  : string) : Promise
   {      
      return new Promise((resolve, reject) =>
      {
         firebase
         .auth()
         .signInWithEmailAndPassword(email, password)
         .then((val : any) =>
         {
            resolve(val);
         })
         .catch((error : any) =>
         {
            reject(error);
         });
      });
   }



   
   /**
    * Log out with Firebase Web API signOut method 
    * 
    * @method logOut
    * @return {Promise}
    */
   logOut() : Promise
   {
      return new Promise((resolve, reject) =>
      {
        firebase
        .auth()
        .signOut()
        .then(() =>
        {
           resolve(true);
        })
        .catch((error : any) =>
        {
           reject(error);
        });
      }); 
   }

}

These should be fairly simple and straightforward to understand (the method names and commenting should guide you if you're unsure as to what each section of the above service is designed to do) so we can now move onto configuring the LoginPage component.

Logging in

As the LoginPage component is the sole point of access for our project we'll need to implement an e-mail/password sign-in form that makes use of the loginWithEmailAndPasswordmethod from our AuthProvider service. In doing so we'll also take advantage of Angular's FormBuilder class to ensure that the sign-in form is unable to be submitted until the e-mail/password fields have been completed.

In full then (and it's actually quite a minimal amount of code we're adding to the class) our tabs-demo/src/pages/login/login.ts component should resemble the following:

import { Component } from '@angular/core';
import { IonicPage, NavController } from 'ionic-angular';
import {
   FormBuilder,
   FormGroup, 
   Validators } from '@angular/forms';

import { AuthProvider } from '../../providers/auth/auth';

// Import the TabsPage component
import { TabsPage } from '../tabs/tabs';

@IonicPage()
@Component({
  selector: 'page-login',
  templateUrl: 'login.html',
})
export class LoginPage {
  
   /**
    * Create reference for FormGroup object
    */
   public form                  : FormGroup;
   

   constructor(public navCtrl    : NavController,
               private _FB       : FormBuilder,
               private _AUTH     : AuthProvider) 
   {
      // Define FormGroup object using Angular's FormBuilder
      this.form = this._FB.group({
         'email'        : ['', Validators.required],
         'password'     : ['', Validators.required]
      }); 
   }




   /**
    * Log in using the loginWithEmailAndPassword method 
    * from the AuthProvider service (supplying the email 
    * and password FormControls from the template via the 
    * FormBuilder object
    * @method logIn
    * @return {none}
    */
   logIn() : void
   {
      let email      : any        = this.form.controls['email'].value,
          password   : any        = this.form.controls['password'].value;

      this._AUTH.loginWithEmailAndPassword(email, password)
      .then((auth : any) => 
      {
         this.navCtrl.setRoot(TabsPage);
      })
      .catch((error : any) => 
      {
         console.log(error.message);
      });
   }

}

We could enhance the handling of rejected promises in the logIn method by utilising something along the lines of Ionic's AlertController class to display an error dialog on the screen should Firebase authentication fail.

I'll leave this to you to experiment with adding to the project.

In the corresponding HTML template for the component - tabs-demo/src/pages/login/login.html - we'll add the necessary templating for the form (along with an Ionic brand logo) which makes use of Ionic's pre-built components and integrates in with the FormBuilder object created in the component's class:

<ion-header>
   <ion-navbar>
      <ion-title>
         Demo
      </ion-title>
   </ion-navbar>
</ion-header>

<ion-content padding>
   

   <img 
      class="logo"
      src="assets/images/icon.png"
      alt="Ionic Framework logo">


   <div
      class="login">

      <h1>Welcome</h1>

      <p>Please log in to access the Demo.</p>

      <form 
        [formGroup]="form" 
        (ngSubmit)="logIn()"> 
        <ion-list>
            <ion-item margin-bottom> 
               <ion-label>Your E-mail Address</ion-label> 
               <ion-input
                  type="email"
                  formControlName="email"></ion-input> 
            </ion-item>
   
   
            <ion-item margin-bottom>
               <ion-label>Your Password</ion-label> 
               <ion-input
                  type="password"
                  formControlName="password"></ion-input> 
            </ion-item>
   
   
            <button
               ion-button
               color="primary"
               text-center
               block [disabled]="!form.valid">Log In</button>
         </ion-list> 
      </form>

   </div>


</ion-content>

The HTML should be fairly self-explanatory but there are a couple of points to pay attention to:

  • Use of the formGroup property binding to connect the form template with the Formbuilder object in the component's class
  • The form's ngSubmit event executes the logIn method of the component class
  • formControlName attributes on each field tie the field value to the corresponding key in the Formbuilder object
  • Submit button is disabled until the form fields have been completed

The image used inside the template:

<img 
   class="logo"
   src="assets/images/icon.png"
   alt="Ionic Framework logo">

Is stored within a custom directory titled images (that you'll need to create within the tabs-demo/src/assets directory) and consists of the Ionic Framework logo (which you can simply download from Google Images and use in the project thereafter).

The LoginPage HTML template makes use of 2 CSS classes - login and logo - which define the styling for the page, both of which are declared within the component Sass file tabs-demo/src/pages/login/login.scss like so:

page-login {

   .logo {
	  display: block;
	  margin: 5em auto;
	  width: 150px;
   }


   .login {
      background: rgba(255, 255, 255, 1);
      border-radius: 10px; 
      margin: 1em;
      padding: 2em 5em 5em 2em;   
   
      p {
      	  margin-bottom: 3em; 
      }
   
   
      button {
      	  margin: 0 0 1.5em 0;
      }
   }

}

With the coding in place for the LoginPage component our project is close to completion - all that remains now are the final set of amendments which we'll be making to the TabsPage component.

Configuring project tabs

In the TabsPage component class we need to define a logout mechanism and import the LoginPage for redirecting the user on successful logout from Firebase.

This is accomplished within the tabs-demo/src/pages/tabs/tabs.ts file like so:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { AboutPage } from '../about/about';
import { ContactPage } from '../contact/contact';
import { HomePage } from '../home/home';
import { LoginPage } from '../login/login';

import { AuthProvider } from '../../providers/auth/auth';


@Component({
  templateUrl: 'tabs.html'
})
export class TabsPage {
 

   /**
    * Define 1st Tab's rootPage value
    */
   tab1Root = HomePage;



   /**
    * Define 2nd Tab's rootPage value
    */
   tab2Root = AboutPage;



   /**
    * Define 3rd Tab's rootPage value
    */
   tab3Root = ContactPage;
 


   constructor(private _NAV      : NavController,
               private _AUTH     : AuthProvider) 
   {  }
 


 
   /**
    * Log out from Firebase/set the rootPage value to
    * the LoginPage component
    * @method logOut
    * @return {none}
    */
   logOut() : void
   {
      this._AUTH.logOut()
      .then((data : any) =>
      {
         this._NAV.setRoot(LoginPage);
      })
      .catch((error : any) =>
      {
         console.dir(error);
      });
   }


}

Now all that remains is to edit the tabs-demo/src/pages/tabs/tabs.html template to include a logout tab like so:

<ion-tabs>
  <ion-tab [root]="tab1Root" tabTitle="Home" tabIcon="home"></ion-tab>
  <ion-tab [root]="tab2Root" tabTitle="About" tabIcon="information-circle"></ion-tab>
  <ion-tab [root]="tab3Root" tabTitle="Contact" tabIcon="contacts"></ion-tab>  
  <ion-tab tabTitle="Logout" tabIcon="close" (ionSelect)="logOut()"></ion-tab>
</ion-tabs>

With the project logic now in place we can test that this works by running the application in our system browser with the following Ionic CLI command:

ionic serve

Which, if our code is error free, should display the LoginPage component for the application like so:

Ionic login screen to access Firebase service

From there it's simply a case of providing the e-mail/password credentials you registered with the Firebase service in the Setting up Firebase section of this tutorial to access the 'logged in' part of the application like so:

Ionic logged in screen after successful Firebase authentication

If you're seeing this then you've successfully implemented a log-in mechanism into an Ionic Tabs page layout!

In summary

During this tutorial we've looked into using Firebase to authenticate logging in to an Ionic application built using a Tabs template.

This has involved 'hiding' the Tabs layout until the user has passed Firebase authentication - the functionality for which is supplied courtesy of a custom AuthProvider service.

We could enhance and extend the above tutorial to include features such as the following:

  • Functionality to register an account which allows user's to join the service
  • Provide feedback for authentication errors (I.e. screen alerts or notifications informing the user of what has happened and what they might do to rectify this)
  • Add social sign-in features to the LoginPage component which would allow user's to sign-in using social media providers such as Facebook, Twitter or Google+

There are lots of ways in which this tutorial could be built on so feel free to explore and play!

If you enjoyed what you've read here then please sign up to my mailing list and, if you haven't done so already, take a look at my e-book: Mastering Ionic for information about working with forms and components in Ionic (doesn't cover Firebase).

Tags

Categories

Post a comment

All comments are welcome and the rules are simple - be nice and do NOT engage in trolling, spamming, abusiveness or illegal behaviour. If you fail to observe these rules you will be permanently banned from being able to comment.

Top