Search code examples
javascriptangularfirebasefirebase-authenticationangularfire2

Angular always returning undefined or null in authentication system with firebase


I'm building an authentication system with email and password in firebase the login and logout both work fine as response console.log(authState) but the angular guard always returns the method isLoggedIn() is null I don't know why is that?! In the constructor I have defined it with userDetails value:

Code for just the auth.service constructor:

  public user: Observable<firebase.User>;
  public userDetails: firebase.User = null;


  constructor(
    private af: AngularFireAuth,
    private navCtrl: NavController,
    private statusMessage: ToastMessagesService
  ) {


    this.user = af.authState;

    this.user.subscribe(
      user => {
        this.userDetails = user;
        console.log(this.userDetails); // I get a response from this on the login page with user details ( user is logged in )
      }
    )

  }

Code of the method alone:

  isLoggedIn(): boolean {
    return (this.userDetails != null) ? true : false;
  }

The code of the whole service ( auth.service.ts ):

import { Injectable } from '@angular/core';
import { AngularFireAuth } from 'angularfire2/auth';
import { Observable } from 'rxjs/internal/observable';
import { NavController } from '@ionic/angular';
import { ToastMessagesService } from './toast-messages.service';
import * as firebase from 'firebase';

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

  public user: Observable<firebase.User>;
  public userDetails: firebase.User = null;


  constructor(
    private af: AngularFireAuth,
    private navCtrl: NavController,
    private statusMessage: ToastMessagesService
  ) {


    this.user = af.authState;

    this.user.subscribe(
      user => {
        this.userDetails = user;
        console.log(this.userDetails); 
      }
    )

  }

  async siginInRegular(username: string, password: string) {
    // const credentials = this.af.auth.email
    const results = await this.af.auth.signInWithEmailAndPassword(username, password).then(
      results => {
        this.navCtrl.navigateForward('/home');
        this.statusMessage.message(`Welcome ${results.user.email}`);
      }
    );

  }

  async register(username: string, password: string) {
    try {
      return await this.af.auth.createUserWithEmailAndPassword(username, password).then(
        user => {
          this.navCtrl.navigateForward('/profile');
          this.statusMessage.message(`Welcome ${user.user.email}`);
        }
      );
    } catch (error) {
      console.dir(error);
    }
  }

  signOut() {
    return this.af.auth.signOut();
  }

  isLoggedIn(): boolean {
    return (this.userDetails != null) ? true : false;
  }



}

The code for the guard:

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {


  constructor(
    private auth: AuthService
  ) {
    console.log(auth.isLoggedIn());

  }

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {


    if (this.auth.isLoggedIn()) {
      return true
    }

    console.log('Access denied!');
    return false;
  }
}

Solution

  • There could be a race in your code. this.userDetails in your AuthService is not "ready" yet when you call it from your guard.

    What you can do is, return a promise/observable in the canActivate method, as it accepts Observable<boolean> | Promise<boolean> | boolean.

    a basic example could be this:

    1. Add a method to get the auth state in your auth service:

       get authState(): Observable<any> {
         return this.af.authState;
       }
      
    2. Use it in your canActivate() method:

      canActivate(): Observable<boolean> {
        return this.authService.authState()
          .pipe(
               map(authState => !!authState),
               tap(auth => !auth ? console.log('Access Denied!') : true),
               take(1)
           );
      }