Search code examples
angularangular2-routingangular8rxjs6angular-router-guards

Angular 8 auth guard - determine if user came from outside or from inside the app


Seems like a dumb question, but for some reason I can't figure out the answer.

I have an auth guard, which checks the expiration of the JWT token stored in local storage and throws "Your session has expired" dialog which redirects the user to login onClose.

In the current state, the dialog is thrown only if the browser has this token in the storage; otherwise it redirects to login page straight.

canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): boolean {
        if (!this._auth.isAuthenticated()) {
            if (this._auth.hasToken()) {
                this._dialogService.openErrorDialog('Your session has expired, please re-login', 0)
                    .afterClosed()
                    .subscribe(() => this.navigateToLogin(state.url));
            } else {
                this.navigateToLogin(state.url);
            }

            return false;
        }

    return true;
}

I want to implement the following logic: token expired while user browsed on some app page -> user navigated to another page -> dialog thrown -> redirect to login; but if user navigates to some page in the app from the outside, it should redirect him the login page straight, without throwing the dialog, even if there's already an expired token in the browser local storage.

I've tried to hack some solutions based on this, but it didn't work out - although I can return an Observable<boolean> from canActivate function, I couldn't figure out how can I make the rxjs map work with the results of pairwise - so I could use the fact of existence of the previous URL to determine where the user came from.

Any help greatly appreciated.


Solution

  • Created stackblitz sample for you. Used the info you shared in the link and created a service like below.

    @Injectable({ providedIn: "root" })
    export class FirstVisitService {
    
      readonly isFirstVisit = new BehaviorSubject<boolean>(true);
    
      constructor(router: Router) {
        router.events
          .pipe(
            filter(event => event instanceof NavigationEnd),
            first()
          )
          .subscribe((e: NavigationEnd) => {
            this.isFirstVisit.next(false);
            this.isFirstVisit.complete();
          });
      }
    }
    

    and injected it in the auth.guard and used like this :

    constructor(private service: FirstVisitService   ) { }
    
      canActivate(
        next: ActivatedRouteSnapshot,
        state: RouterStateSnapshot
      ): Observable<boolean> | Promise<boolean> | boolean {
    
        if (this.service.isFirstVisit.value) {
          console.log("first time");
        }  else{
          console.log("not first time");
        }
    
        return true;
      }
    

    and you have to import it in app module

    export class AppModule {
      constructor(f : FirstVisitService) {}
    }
    

    So it takes place and subscribes the router event before any event happened.