Phone Authorization with Firebase and Ionic

Demand for web and mobile applications is still increasing. Developers nowadays don’t build whole systems from scratch since it’s time and cost consuming. With rise of platforms such as Firebase, Azure or AWS we have a way of developing apps quicker and with more confidence.

|
   Mobile  Front-end 

Repetitive tasks such as user authorization became as easy as ever. Let’s take a look on how we can utilize Firebase to authenticate user in Uber/Whatsapp way with just a phone number.

For sake of this article we’ll use following tech:

-       Ionic - as it is good for prototyping - will be our hybrid app core. Feel free to use one of your choice.

-       Firebase library for static method PhoneAuthProvider.credential() that is not exposed in AngularFire (to omit reCaptcha since it’s bad UX for mobile apps)

-       IonicNative Firebase plugin for SMS code

-       AngularFire library for auth handling

Let’s start with setting up our base app

npm install -g ionic

Then let’s bootstrap our app with blank template

ionic start demo-phone-auth blank

After setup process start app with

ionic serve­­

Let’s generate LoginPage with phoneNumber input and button to login. We’ll use Ionic CLI for sake of simplicity.

ionic g page login

in src/app/app.component.ts change rootPage to LoginPage

rootPage: any = LoginPage;

then in src/app/app.module.ts add LoginPageModule to imports array.

Let’s prepare simple input for phone number and button for login page. We’ll use ready components from Ionic docs.

<ion-content padding>
  
  <ion-list>
    <ion-item>
      <ion-label>+48</ion-label>
      <ion-input #phoneNumber type="tel" maxlength="9"></ion-input>
    </ion-item>
  </ion-list>

</ion-content>

<ion-footer class="login-footer">
  <button 
    full
    ion-button 
    class="login-btn"
    (click)="registerPhone()">Go</button>
</ion-footer>

We’ll indicate phone number prefix, we hook #phoneNumber to @ViewChild(‘phoneNumber’) in our controller (which we’ll start building later). We setup filed to have type tel to display to the user numeric keyboard when field is focused. And set maxlength to 9 (which is polish phone number length).

In footer tag we add click binding and assing registerPhone() method (which we’ll build later).

Let’s setup Firebase project. Go to Firebase Console and add new project. Then choose Develop -> Authentication tab from left side menu. On the right top side click “Web setup” and copy your credentials to src/app/env.config.ts

export const FIREBASE_CONFIG = {
  apiKey: "<>",
  authDomain: "<>",
  databaseURL: "<>",
  projectId: "<>",
  storageBucket: "<>",
  messagingSenderId: "<>"
};

Let’s install firebase sdk and AngularFire and firebase plugin
npm i firebase @angular/fire @ionic-native/firebase[MA1] 

then add cordova plugin

ionic cordova plugin add cordova-plugin-firebase

Let’s add those to our app module in src/app/app.module.ts

imports: [
    BrowserModule,
    LoginPageModule,
    HomePageModule,
    AngularFireModule.initializeApp(FIREBASE_CONFIG),
    AngularFireAuthModule,
    IonicModule.forRoot(MyApp)
  ],

FIREBASE_CONFIG is your config from Firebase Console.

Bonus. Since we can’t access cordova plugins in browser we’ll get errors whenever we’ll try to access methods that are using native sdk’s. I’ll give you an example on how to overcome this issue in developer friendly way.

Create two files in src/mock.ts and src/mock.providers.ts

mock.ts file will look like this

import {Firebase} from "@ionic-native/firebase";

export class FirebaseMock extends Firebase {

  verifyPhoneNumber(): Promise<any> {
    return Promise.resolve({verificationId: 'example'})
  }
}

and mock.providers.ts will look like this

import {FirebaseMock} from "./mock";
import {Firebase} from '@ionic-native/firebase';


export const isBrowser = (): boolean => {
  return (
    (document.URL.includes('https://') || document.URL.includes('http://localhost')) &&
    !document.URL.includes('/Application/')
  );
};

const FirebaseFactory = (): Firebase => {
  return isBrowser() ? new FirebaseMock() : new Firebase();
};
export const FirebaseProvider = {
  provide: Firebase,
  useFactory: FirebaseFactory,
};

now in app.module.ts we can add FirebaseProvider to providers array and our app will use mocked method verifyPhoneNumber() whenever called from browser enviroment but will use original method on real device.

Okay. Let’s continue building our app. In our login controller (login.ts).

import {AngularFireAuth} from '@angular/fire/auth';
import {Firebase} from '@ionic-native/firebase';
import firebase from 'firebase/app';
 @ViewChild('phoneNumber') phoneNumber: TextInput;

  constructor(
    private navCtrl: NavController,
    private alertCtrl: AlertController, 
    private fireAuth: AngularFireAuth,
    private fireNative: Firebase,
  ) {
  }

we’re binding phoneNumber input and injecting couple services. NavController will be used for our app navigation. AlertController will be used to type SMS code for validation (simple prompt). AngularFireAuth will be used to sign in. Firebase will be used to ask for SMS code.

We bound registerPhone() method in our template. Let’s create it now in our controller.

private showPrompt(verificationId: string): void {

    let promptCode = this.alertCtrl.create({
      title: 'Verify',
      message: 'Type code that was received via SMS',
      inputs: [
        {
          name: 'code',
          type: 'tel',
          placeholder: 'Code'
        },
      ],
      buttons: [
        {
          text: 'Cancel',
          role: 'cancel'
        },
        {
          text: 'Verify',
          handler: data => {
            this.verifyCode(data.code, verificationId);
          }
        }
      ]
    });
    promptCode.present();
  }

And we implement verifyCode() method

private async verifyCode(code: string, verificationId: string): Promise<void> {
    const credential = await firebase.auth.PhoneAuthProvider.credential(verificationId, code);
    await this.fireAuth.auth.signInAndRetrieveDataWithCredential(credential);
  }

We pass SMS code and verificationId to static method credential() that is only available from firebase js sdk (AngularFire do not expose it) and wait for credential to be returned. Then we use signInAndRetrieveDataWithCredential() from AngularFire to retrieve UserCredential. Then we implement doLogin() method to navigate user to our app.

private doLogin(): void {
    this.navCtrl.setRoot('HomePage');
 }

We can wait to user to be authorized subscribing to authState from AngularFire

ngOnInit() {
    this.authSub = this.fireAuth.authState.subscribe((user: firebase.User) => {
      if (user) {
        this.doLogin();
      }
    });
  }

  ngOnDestroy() {
    this.authSub.unsubscribe();
  }

So our app code is ready. Now we have to configure Firebase to correctly handle our phone auth.

Go to your firebase console and in Authentication tab go to login methods and turn on Phone Auth.

In Firebase console add new Android app. Make sure your package ID match the one in Firebase Console - com.ionicfirebasephoneauth.demo.

Add SHA-1 blueprint - how to

In your android app settings download google-services.json and paste it in root folder of project.

Run app and test app ionic cordova run android --device

Full code is available here

As you can see prototyping is as easy as ever with today’s technology. User authorization is no longer time consuming process with such tooling developers can focus more on key features of their apps.

Szymon Bury

Did you like this article?

Phone Authorization with Firebase and Ionic