25

I've read the guide here: https://angular.io/docs/ts/latest/guide/router.html

Seems pretty straightforward, however, I'm not sure how to use angularfire2's authentication within an auth guard (canActivate). What I've tried is:

AuthService

import { Injectable } from '@angular/core';
import { AngularFire } from 'angularfire2';

@Injectable()
export class AuthService {

  private user: any;

  constructor(private af: AngularFire) {
    this.af.auth.subscribe(user => {
      this.user = user;
    })
  }

  get authenticated(): boolean {
    return this.user ? true : false;
  }

}

AuthGuard

@Injectable()
export class AuthGuard implements CanActivate {

  constructor(private router: Router, private authService: AuthService) { }

  canActivate(): Observable<boolean> | boolean {
    if (this.authService.authenticated)
      return true;

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

}

I've also added AuthService to bootstrap providers.

This sort of works fine, however, my main problem is when I refresh (or initially load) the page on that has AuthGuard it always redirects me to the login page since the AuthGuard doesn't wait for the authentication response. Is there a way to wait for the authentication to finish (even if it's failed) and then check whether the user is authenticated?

Rob
  • 14,746
  • 28
  • 47
  • 65
Andrew
  • 2,063
  • 3
  • 24
  • 40

3 Answers3

22

The problem is with your code. In the AuthGuard you check the result of authenticated() method that will most probably return false as the user property is still not set. Try this:

AuthService:

import { Injectable } from '@angular/core';
import { AngularFire } from 'angularfire2';';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class AuthService {

  private user: any;

  constructor(private af: AngularFire) { }
  setUser(user) { this.user = user; }
  getAuthenticated(): Observable<any> { return this.af.auth; }
}

AuthGuard:

@Injectable()
export class AuthGuard implements CanActivate {

  constructor(private router: Router, private authService: AuthService) { }

  canActivate(): Observable<boolean> | boolean {
    // here check if this is first time call. If not return 
    // simple boolean based on user object from authService
    // otherwise:

    return this.authService.getAuthenticated.map(user => {
          this.authService.setUser(user);
          return user ? true : false;
    })

  } 
}
Baumi
  • 1,745
  • 1
  • 17
  • 31
  • Thanks! Is there a way to modify this so that I get the user data in the AuthService constructor? With the way you solved it I don't get the user data only on guarded routes, but, for example, I have a header which would show the user's email address on non-guarded routes as well. – Andrew Jun 19 '16 at 11:33
  • Also, this doesn't seem to work, since the `canActivate()` method has to return `Observable | boolean`, but in your example it doesn't return anything. How can I solve that? – Andrew Jun 19 '16 at 15:16
  • Sorry, I forgot to return a result in canActivate. – Baumi Jun 19 '16 at 16:32
  • How to redirect to the login page, if an Observable is return false ? – Efriandika Pratama Jun 22 '16 at 06:02
  • @EfriandikaPratama this.router.navigate(['login']); – gandarez Dec 11 '17 at 18:05
  • 1
    This also seems to delegate the authentication to the guard. Although the actual call definition for authentication has be delegated back to the AuthService, you are setting the user in the guard. Other guards would have to also implement this same logic. – Chewy Mar 13 '19 at 18:17
9

It may be different in some versions of the router but I needed to return an observable that completes.

import { CanActivate, Router } from '@angular/router'

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private af: AngularFire, private router: Router) { }

  canActivate(): Observable<boolean> {
    return this.af.auth.map(user => {
      if (user != null)
        return true
      this.router.navigate(['/login'])
    })
    .take(1) // To make the observable complete after the first emission
  }
}
hayden
  • 2,643
  • 17
  • 21
1

2021 UPDATE for AngularFire, RXJS and Firebase

Taken from Baumi's answer

**AuthService**:
import { Injectable } from '@angular/core';

import { AngularFireAuth } from '@angular/fire/auth';
import * as firebase from 'firebase/';

import { Observable } from 'rxjs';
    
@Injectable()
export class AuthService {

    private user: firebase.default.User;

    constructor(private afAuth: AngularFire) { }

    setUser(user) { 
        this.user = user; 
    }
    
    getAuthenticated(): Observable<firebase.default.User> { 
        return this.afAuth.user; 
        }
}


**AuthGuard**:
import { map } from 'rxjs/operators';

@Injectable()
export class AuthGuard implements CanActivate {

    constructor(
        private router: Router, 
        private authService: AuthService
    ) {}

    canActivate(): Observable<boolean> {
        // here check if this is first time call. If not return 
        // simple boolean based on user object from authService
        // otherwise:

        return this.authService.getAuthenticated().pipe(
            map(user => {
                this.authService.setUser(user);
                return user ? true : false;
            })
        )
    } 
}
Carlo Nyte
  • 665
  • 6
  • 17