How to Integrate Firebase Auth in Ionic App (Login/Signup)

In this article we going to learn how to integrate Firebase Authentication in an Ionic app so we can add login and signup functionality using email and password.

Authentication is one of those things every app needs and it always takes longer than expected to set up from scratch — managing tokens, storing sessions, handling password resets, securing routes. Firebase Auth handles all of that for us. We set it up once, and Firebase takes care of user management, session persistence, and token handling automatically.

The best part — Firebase Auth works perfectly with Ionic and Capacitor. Whether our app runs on Android, iOS, or as a web app in the browser, the same code works across all three. We don't write platform-specific auth code at all.

This tutorial shows how to:

  • Create a Firebase project and enable Email/Password authentication
  • Install and configure Firebase in an Ionic Angular project
  • Create an AuthService to handle login, signup, and logout
  • Build a Signup page with form validation
  • Build a Login page
  • Protect routes using Angular Route Guards with Firebase Auth
  • Handle auth state persistence so users stay logged in after app restart
  • Show the current user's profile information

Why Firebase Auth for Ionic

We could build our own auth backend — a .NET API with JWT tokens, refresh tokens, password hashing. It works but it's weeks of development. Firebase Auth gives us all of that in about an hour of setup.

Email/Password auth is just the start. Once Firebase is in our project, adding Google Sign-In, Apple Sign-In, or phone number auth later is just a few more lines of config. Firebase scales too — millions of users, same setup, no changes on our side.


Step 1 : Create Firebase Project and Enable Auth

Go to console.firebase.google.com and sign in.

Click Add project. Give it a name like ionic-auth-demo. Disable Google Analytics for now (optional) and click Create project.

Once the project is ready, click Continue. On the project dashboard, click the Web icon </> to add a web app. Give it a nickname — something like ionic-app. Click Register app.

Firebase shows us the config object. Copy it — we'll need it in a moment. It looks like this :

const firebaseConfig = {
  apiKey: "AIzaSyXXXXXXXXXXXXXXXXXXXXXXX",
  authDomain: "ionic-auth-demo.firebaseapp.com",
  projectId: "ionic-auth-demo",
  storageBucket: "ionic-auth-demo.appspot.com",
  messagingSenderId: "123456789",
  appId: "1:123456789:web:abcdefgh"
};

Now enable Email/Password authentication. In the left menu go to Build → Authentication. Click Get started. Under Sign-in method, click Email/Password, toggle it to Enabled, and click Save.

That's all the Firebase Console setup we need.


Step 2 : Install Firebase in the Ionic Project

Create a new Ionic project if we don't have one already :

ionic start ionic-firebase-auth blank --type=angular
cd ionic-firebase-auth

Install the Firebase JavaScript SDK :

npm install firebase

Now create the Firebase configuration file. In src/environments/environment.ts, add the Firebase config :

export const environment = {
  production: false,
  firebaseConfig: {
    apiKey: "AIzaSyXXXXXXXXXXXXXXXXXXXXXXX",
    authDomain: "ionic-auth-demo.firebaseapp.com",
    projectId: "ionic-auth-demo",
    storageBucket: "ionic-auth-demo.appspot.com",
    messagingSenderId: "123456789",
    appId: "1:123456789:web:abcdefgh"
  }
};

Do the same in src/environments/environment.prod.ts with the same config values (or production Firebase config if we have a separate project for production).


Step 3 : Initialize Firebase in the App

In Angular 17+ standalone apps, we initialize Firebase in main.ts or app.config.ts. Create a Firebase initialization file first.

Create src/app/firebase.config.ts :

import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import { environment } from '../environments/environment';

const app = initializeApp(environment.firebaseConfig);
export const auth = getAuth(app);

This initializes Firebase and exports the auth instance we'll use throughout the app.


Step 4 : Create the AuthService

The AuthService is where all Firebase Auth calls live — signup, login, logout, and watching the auth state. Create it :

ng generate service services/auth

Open src/app/services/auth.service.ts and replace with this :

import { Injectable, signal } from '@angular/core';
import {
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  signOut,
  onAuthStateChanged,
  User,
  sendPasswordResetEmail
} from 'firebase/auth';
import { auth } from '../firebase.config';
import { Router } from '@angular/router';

@Injectable({ providedIn: 'root' })
export class AuthService {

  currentUser = signal<User | null>(null);
  isLoggedIn = signal<boolean>(false);

  constructor(private router: Router) {
    // Listen to auth state changes
    // Fires on login, logout, and when app restarts if user was previously logged in
    onAuthStateChanged(auth, (user) => {
      this.currentUser.set(user);
      this.isLoggedIn.set(!!user);
    });
  }

  async signup(email: string, password: string): Promise<void> {
    const credential = await createUserWithEmailAndPassword(auth, email, password);
    this.currentUser.set(credential.user);
    await this.router.navigate(['/home']);
  }

  async login(email: string, password: string): Promise<void> {
    const credential = await signInWithEmailAndPassword(auth, email, password);
    this.currentUser.set(credential.user);
    await this.router.navigate(['/home']);
  }

  async logout(): Promise<void> {
    await signOut(auth);
    this.currentUser.set(null);
    this.isLoggedIn.set(false);
    await this.router.navigate(['/login']);
  }

  async resetPassword(email: string): Promise<void> {
    await sendPasswordResetEmail(auth, email);
  }

  getUser(): User | null {
    return this.currentUser();
  }
}

A few things to note here. We're using Angular Signals (signal()) for currentUser and isLoggedIn. This means any component that reads these signals will automatically update when the auth state changes — no subscriptions needed.

onAuthStateChanged is the key part. Firebase calls this callback every time the auth state changes — user logs in, logs out, or when the app starts and Firebase restores a previously logged-in session. This is how session persistence works automatically without us doing anything extra.


Step 5 : Create the Signup Page

Generate the signup page :

ionic generate page pages/signup

Open src/app/pages/signup/signup.page.ts :

import { Component } from '@angular/core';
import { ReactiveFormsModule, FormGroup, FormControl, Validators } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { AuthService } from '../../services/auth.service';
import { RouterLink } from '@angular/router';

@Component({
  selector: 'app-signup',
  standalone: true,
  imports: [IonicModule, ReactiveFormsModule, CommonModule, RouterLink],
  template: `
    <ion-header>
      <ion-toolbar color="primary">
        <ion-title>Create Account</ion-title>
      </ion-toolbar>
    </ion-header>

    <ion-content class="ion-padding">
      <div style="max-width: 400px; margin: 40px auto;">
        <h2>Sign Up</h2>

        <form [formGroup]="signupForm" (ngSubmit)="onSignup()">
          <ion-item>
            <ion-label position="floating">Email</ion-label>
            <ion-input formControlName="email" type="email"></ion-input>
          </ion-item>
          <div *ngIf="signupForm.get('email')?.invalid && signupForm.get('email')?.touched">
            <ion-text color="danger"><small>Valid email is required</small></ion-text>
          </div>

          <ion-item style="margin-top: 12px;">
            <ion-label position="floating">Password</ion-label>
            <ion-input formControlName="password" type="password"></ion-input>
          </ion-item>
          <div *ngIf="signupForm.get('password')?.invalid && signupForm.get('password')?.touched">
            <ion-text color="danger"><small>Password must be at least 6 characters</small></ion-text>
          </div>

          <ion-button
            expand="block"
            type="submit"
            style="margin-top: 20px;"
            [disabled]="signupForm.invalid || loading">
            {{ loading ? 'Creating Account...' : 'Sign Up' }}
          </ion-button>
        </form>

        <ion-text color="danger" *ngIf="errorMessage">
          <p style="text-align: center; margin-top: 10px;">{{ errorMessage }}</p>
        </ion-text>

        <p style="text-align: center; margin-top: 20px;">
          Already have an account? <a routerLink="/login">Login</a>
        </p>
      </div>
    </ion-content>
  `
})
export class SignupPage {
  loading = false;
  errorMessage = '';

  signupForm = new FormGroup({
    email:    new FormControl('', [Validators.required, Validators.email]),
    password: new FormControl('', [Validators.required, Validators.minLength(6)])
  });

  constructor(private authService: AuthService) {}

  async onSignup() {
    if (this.signupForm.invalid) return;

    this.loading = true;
    this.errorMessage = '';

    const { email, password } = this.signupForm.value;

    try {
      await this.authService.signup(email!, password!);
    } catch (error: any) {
      this.errorMessage = this.getErrorMessage(error.code);
    } finally {
      this.loading = false;
    }
  }

  private getErrorMessage(code: string): string {
    switch (code) {
      case 'auth/email-already-in-use':
        return 'This email is already registered. Try logging in instead.';
      case 'auth/invalid-email':
        return 'Please enter a valid email address.';
      case 'auth/weak-password':
        return 'Password must be at least 6 characters.';
      default:
        return 'Signup failed. Please try again.';
    }
  }
}

The getErrorMessage() method maps Firebase error codes to readable messages. Firebase returns specific error codes like auth/email-already-in-use — much better to show "This email is already registered" than displaying a raw Firebase error object to users.


Step 6 : Create the Login Page

Generate the login page :

ionic generate page pages/login

Open src/app/pages/login/login.page.ts :

import { Component } from '@angular/core';
import { ReactiveFormsModule, FormGroup, FormControl, Validators } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { AuthService } from '../../services/auth.service';
import { RouterLink } from '@angular/router';

@Component({
  selector: 'app-login',
  standalone: true,
  imports: [IonicModule, ReactiveFormsModule, CommonModule, RouterLink],
  template: `
    <ion-header>
      <ion-toolbar color="primary">
        <ion-title>Login</ion-title>
      </ion-toolbar>
    </ion-header>

    <ion-content class="ion-padding">
      <div style="max-width: 400px; margin: 40px auto;">
        <h2>Welcome Back</h2>

        <form [formGroup]="loginForm" (ngSubmit)="onLogin()">
          <ion-item>
            <ion-label position="floating">Email</ion-label>
            <ion-input formControlName="email" type="email"></ion-input>
          </ion-item>

          <ion-item style="margin-top: 12px;">
            <ion-label position="floating">Password</ion-label>
            <ion-input formControlName="password" type="password"></ion-input>
          </ion-item>

          <ion-button
            expand="block"
            type="submit"
            style="margin-top: 20px;"
            [disabled]="loginForm.invalid || loading">
            {{ loading ? 'Logging in...' : 'Login' }}
          </ion-button>
        </form>

        <ion-text color="danger" *ngIf="errorMessage">
          <p style="text-align: center; margin-top: 10px;">{{ errorMessage }}</p>
        </ion-text>

        <p style="text-align: center; margin-top: 10px;">
          <a (click)="onForgotPassword()" style="cursor:pointer; color: var(--ion-color-primary);">
            Forgot password?
          </a>
        </p>

        <p style="text-align: center; margin-top: 20px;">
          Don't have an account? <a routerLink="/signup">Sign Up</a>
        </p>
      </div>
    </ion-content>
  `
})
export class LoginPage {
  loading = false;
  errorMessage = '';

  loginForm = new FormGroup({
    email:    new FormControl('', [Validators.required, Validators.email]),
    password: new FormControl('', [Validators.required])
  });

  constructor(private authService: AuthService) {}

  async onLogin() {
    if (this.loginForm.invalid) return;

    this.loading = true;
    this.errorMessage = '';

    const { email, password } = this.loginForm.value;

    try {
      await this.authService.login(email!, password!);
    } catch (error: any) {
      this.errorMessage = this.getErrorMessage(error.code);
    } finally {
      this.loading = false;
    }
  }

  async onForgotPassword() {
    const email = this.loginForm.get('email')?.value;
    if (!email) {
      this.errorMessage = 'Enter your email first, then click Forgot Password.';
      return;
    }

    try {
      await this.authService.resetPassword(email);
      this.errorMessage = '';
      alert('Password reset email sent. Check your inbox.');
    } catch (error: any) {
      this.errorMessage = 'Could not send reset email. Check the email address.';
    }
  }

  private getErrorMessage(code: string): string {
    switch (code) {
      case 'auth/user-not-found':
      case 'auth/wrong-password':
      case 'auth/invalid-credential':
        return 'Invalid email or password.';
      case 'auth/too-many-requests':
        return 'Too many failed attempts. Try again later.';
      case 'auth/user-disabled':
        return 'This account has been disabled.';
      default:
        return 'Login failed. Please try again.';
    }
  }
}

Step 7 : Protect Routes with Auth Guard

We don't want users accessing the home page without being logged in. Create an auth guard :

ng generate guard guards/auth

Open src/app/guards/auth.guard.ts :

import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';

export const authGuard: CanActivateFn = () => {
  const authService = inject(AuthService);
  const router = inject(Router);

  if (authService.isLoggedIn()) {
    return true;
  }

  router.navigate(['/login']);
  return false;
};

Because isLoggedIn is a Signal, we call it as a function — isLoggedIn(). The guard reads the current signal value and decides whether to allow navigation.

Update our routes in app.routes.ts :

import { Routes } from '@angular/router';
import { authGuard } from './guards/auth.guard';

export const routes: Routes = [
  {
    path: '',
    redirectTo: 'login',
    pathMatch: 'full'
  },
  {
    path: 'login',
    loadComponent: () => import('./pages/login/login.page').then(m => m.LoginPage)
  },
  {
    path: 'signup',
    loadComponent: () => import('./pages/signup/signup.page').then(m => m.SignupPage)
  },
  {
    path: 'home',
    loadComponent: () => import('./home/home.page').then(m => m.HomePage),
    canActivate: [authGuard]
  }
];

Step 8 : Show User Info and Logout on Home Page

Update the home page to show the logged-in user's email and a logout button :

import { Component } from '@angular/core';
import { IonicModule } from '@ionic/angular';
import { CommonModule } from '@angular/common';
import { AuthService } from '../services/auth.service';

@Component({
  selector: 'app-home',
  standalone: true,
  imports: [IonicModule, CommonModule],
  template: `
    <ion-header>
      <ion-toolbar color="primary">
        <ion-title>Home</ion-title>
        <ion-buttons slot="end">
          <ion-button (click)="logout()">Logout</ion-button>
        </ion-buttons>
      </ion-toolbar>
    </ion-header>

    <ion-content class="ion-padding">
      <div style="text-align: center; margin-top: 60px;">
        <ion-icon name="person-circle-outline" style="font-size: 80px; color: var(--ion-color-primary);"></ion-icon>
        <h2>Welcome!</h2>
        <p>Logged in as :</p>
        <p><strong>{{ authService.currentUser()?.email }}</strong></p>
        <p style="color: grey; font-size: 12px;">UID : {{ authService.currentUser()?.uid }}</p>
      </div>
    </ion-content>
  `
})
export class HomePage {
  constructor(public authService: AuthService) {}

  async logout() {
    await this.authService.logout();
  }
}

We expose authService as public so the template can access it directly. currentUser() is a Signal — calling it in the template means Angular automatically tracks it and updates the view when the user changes.


Common Issues and Fixes

"Firebase: Error (auth/configuration-not-found)"

The Firebase config in environment.ts is wrong or the app isn't initialized before auth is called. Make sure initializeApp() runs before any auth calls. Our firebase.config.ts handles this — import it early in the app.

Auth guard redirects to login even after login

onAuthStateChanged is async — it fires after the guard has already run on app startup. The user is null for a brief moment before Firebase restores the session. Fix this by making the guard wait for the first auth state emission :

import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { Auth, authState } from '@angular/fire/auth';
import { firstValueFrom } from 'rxjs';

export const authGuard: CanActivateFn = async () => {
  const auth = inject(Auth);
  const router = inject(Router);

  const user = await firstValueFrom(authState(auth));

  if (user) return true;

  router.navigate(['/login']);
  return false;
};

"auth/network-request-failed" on Android

The device can't reach Firebase servers. Check internet permission in AndroidManifest.xml :

<uses-permission android:name="android.permission.INTERNET" />

Also check that androidScheme: 'https' is set in capacitor.config.ts — some Firebase calls fail on file:// scheme.

User stays logged in after logout on browser

Firebase Auth uses IndexedDB for session persistence by default. Clearing it on logout happens automatically via signOut(). If we're seeing stale sessions, make sure signOut(auth) is being awaited properly and not fire-and-forget.


Summary

We learned how to integrate Firebase Authentication in an Ionic app with login and signup. We covered :

  • Creating a Firebase project and enabling Email/Password sign-in
  • Installing the Firebase SDK and adding config to environment.ts
  • Initializing Firebase and exporting the auth instance
  • Building AuthService with signup, login, logout, password reset, and auth state tracking using Signals
  • Signup page with reactive form validation and Firebase error code mapping
  • Login page with forgot password functionality
  • Auth guard using Angular's functional CanActivateFn and Signal-based isLoggedIn check
  • Protecting routes with canActivate and lazy loading pages
  • Displaying current user info and logout on the home page
  • Fixing common issues like network errors on Android and auth guard timing

Firebase Auth takes away all the heavy lifting of user management. We focus on building the app — Firebase handles users, sessions, tokens, and password resets. Once this is in place, adding more sign-in methods like Google or Apple is just a few more lines of config.

I hope you like this article...

Happy coding! 🚀

Post a Comment

0 Comments