1

I am trying to implement an auth guard in an Angular app which uses Firebase version 9 as backend. My goal is to set canActivate true when user has a document in Firestore, false otherwise. However I am having difficulties caused by asynchronous data when I try to get current authenticated user and user's documents from Firebase. Auth guard does not wait for User service to fetch current user and it's data. It returns false before User service even gets the current user in it's constructor. I have tried using Promises but not succeeded, I have checked other questions and tried to use Observables but was not able to apply them to my case. Here is my minimal code:

User service:

  export class UserService {

      firebaseApp: any;
      db: FirebaseFirestore;
      auth: Auth;
      user: any;

      constructor() {
        
        this.firebaseApp = initializeApp(environment.firebase);
        this.db = getFirestore(this.firebaseApp);
        this.auth = getAuth(this.firebaseApp);
        onAuthStateChanged(this.auth, user => {
          this.user = user;
        })
      }

      
    async isNewUser(uid: string): Promise<boolean>{
        return new Promise<boolean>( async resolve => { 
          const docRef = doc(this.db, "users", uid);
          const docSnap = await getDoc(docRef);
          if (docSnap.exists()) {
            console.log("Document data:", docSnap.data());
            resolve(false);
          } else {
            // doc.data() will be undefined in this case
            console.log("No such document!");
            resolve(true);
          }
        })
    
      }


   isLoggedIn(): Promise<boolean> {    

    return new Promise<boolean>(async resolve => {
      if(this.user == null){
        resolve(false);
      }else{
        await this.isNewUser(this.user.uid).then(res => {
          resolve(res);
        })
      }
    })
   
  }
}

Auth guard:

  constructor(private userService: UserService){}

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot):  Promise<boolean> {

      return new Promise<boolean>(resolve => {
        this.userService.isLoggedIn().then(res=> {
          resolve(res);
        });
       });

  }

Also, what is the best practice here? How can I guarantee that user is not null, or data is fetched before calling any other function?

AyseAsude
  • 554
  • 4
  • 13
  • Could be the way you applied the route guard to the routes. Returning a promise or observable from a canActivate call works just fine. – Igor Aug 23 '21 at 19:35
  • This might help, if you would go for an `Observable`: https://stackoverflow.com/questions/38425461/angular2-canactivate-calling-async-function – Milan Tenk Aug 23 '21 at 19:37
  • Actually the problem is canActivate cannot return correct Promise or Observable, because I cannot get data before canActivate returns something. I checked that answer, but I don't want to import any Auth related things to Auth guard. This type of solution is the last resort for me. – AyseAsude Aug 23 '21 at 19:40
  • 1
    Did you try to directly return the service instead of returning yet another new Promise? so simply return this.userService.isLoggedIn() in canActivate? – MikeOne Aug 23 '21 at 20:09
  • Have yo tried this: ... return await new Promise(... ? – Fatih Ersoy Aug 23 '21 at 20:13
  • Unfortunately none if those solutions worked, because user is still null when isLoggedIn() or canActivate() returns. What I need is to wait till I got response from Firebase and then return the result. – AyseAsude Aug 24 '21 at 12:36

0 Answers0