Why my object become empty when I reach a specific route?

345 views Asked by At

I have a problem. So, I want to create a route guard in Angular that doesn't allow the user to reach login page if he is logged in. I check if he is logged in if the BehaviourSubject from the AuthService emits a new user object, but when I type in the search bar the URL of the login page, the user object emitted by the subject become empty. Do you know why is this happening?

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

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

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
      return this.authService.user$.pipe(map(user => {
        // check if the user object is empty
        const isAuth = !(!!Object.keys(user).length);
        console.log(isAuth) 
        if(isAuth) {
          return true;
        }

        // navigate to /books if user is logged in
        return this.router.createUrlTree(['/books']);
      }));
  }
  
}

Here is my AuthService logic:

export class AuthService {
  private baseUsersUrl =
    environment.firebase.databaseURL + MAIN_API_ENDPOINTS.users;

  private userSource = new BehaviorSubject<User>(<User>{});
  user$ = this.userSource.asObservable();
  
  tokenExpirationTimer!: any;

  constructor(private httpClient: HttpClient,private afAuth: AngularFireAuth, private router: Router) { }

  login(email: string, password: string){
    return this.httpClient.post<AuthResponseData>(MAIN_API_ENDPOINTS.login,{
      email: email,
      password: password,
      returnSecureToken: true
    }).pipe(
      catchError(errorResponse => {
        console.log(errorResponse);
        let errorMessage = 'An unknown error occured!';

        if(!errorResponse.error || !errorResponse.error.error) {
          return throwError(errorMessage);
        } else {
          errorMessage = 'Email or password is incorrect'
        }

        return throwError(errorMessage);
      }),
      tap((resData) => {
        this.saveUserData(email, resData.localId, resData.idToken, +resData.expiresIn);
      }),
    );
  }

  saveUserData(email: string, localId: string, idToken: string, expiresIn: number) {
    const expirationDate = new Date(new Date().getTime() + expiresIn * 1000); 
    const currentUser: User = {
      email,
      id: localId,
      token: idToken,
      tokenExpirationDate: expirationDate
    };

    //token will expire in 1h
    this.autoLogout(expiresIn * 1000);
    document.cookie = 'token' + '=' + idToken;
  }

  autoLogin() {
    const idToken = document.cookie.split('=')[1];
    if(!idToken) {
      return;
    }

    this.getUserData(idToken).subscribe((user) => {
      this.saveUserData(user.users[0].email, user.users[0].localId, idToken, 100);   
    })
  }

  getUserData(idToken: string) {
    return this.httpClient.post<any>(
      MAIN_API_ENDPOINTS.userData,
      {idToken: idToken});     
    }

And I added this route guard to the route this way:

const routes: Routes = [
  {
    path: 'login',
    component: LoginComponent,
    canActivate: [LoggedInGuard]
  }]
1

There are 1 answers

3
Junaid bin Abdul Razzaq On

Whenever a route is typed in the search bar of the browser, the angular application bootstraps itself with a clean slate. What you actually have to do is try to call the autoLogin() before your code reaches the LoggedInGuard. Whenever a route is hit in an angular application, canActivate is the first thing that runs, if present on the route. I believe your AuthService runs after the LoggedInGuard has finished performing its logic, due to which LoggedInGuard fails to perform the Login Route Validation and let the route open. If you want to try it, you can also try refreshing the browser window when you are on the login page. You will face the same scenario.