0

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]
  }]
DeborahK
  • 57,520
  • 12
  • 104
  • 129
DoboVlad
  • 35
  • 5

1 Answers1

0

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.

  • Any idea on how to call autoLogin before? I use it in app.component.ts, which is the first thing that should run, no? – DoboVlad Jul 15 '21 at 13:06
  • You are right that app.component.ts will run before login. But the lifecycle of angular states that any route that has canActivate on the route will trigger the guard first, and then if the guard permits all the components on that route will render. So in your case what happens is, your LoggedInGuard runs first, which does not let the login route load, which in return restricts the app.component to render. When your app.component is not rendered, your autoLogin function doesn't execute. – Junaid bin Abdul Razzaq Jul 15 '21 at 19:52
  • Try calling the autoLogin function from LoggedInGuard, when the user object is empty, and if the response of autoLogin is still empty, you can let the route resolve, otherwise don't let it resolve. – Junaid bin Abdul Razzaq Jul 15 '21 at 19:55