0

As the title suggests, I want to protect routes via canLoad. This works to some extent. However, I have two issues with the code.

Here are my code snippets

AppComponent

ngOnInit(): void {
    this.authServ.reauthenticate().subscribe()
}

AuthService

user = new BehaviorSubject<User>(null as any);
reauthenticate(): Observable<User> {
    // return HTTP call here and pipe the user data into user BehaviorSubject
}

AuthGuard

canLoad(){
      return this.authServ.user.pipe(
        map((user) => {
          if (!!user?._token) {
            return true;
          }
          this.router.navigate(['/login']);
          return false;
        })
      );
}

As you can guess, the router can access the user but still redirects to the /login path. Knowing it doesn't work, I brute-forced my way, making two HTTP calls to the server (which is generally bad, I know) to trick the system into acknowledging a persistent user. Here is the code snippet.

AuthGuard

canLoad() {
      return this.authServ.user.pipe(
        switchMap((user) => {
          if (!!user && !!user._token) {
            return of(user);
          }
          return this.authServ.reauthenticate();
        }),
        map((user) => {
          if (!!user?._token) {
            return true;
          }
          this.router.navigate(['/login']);
          return false;
        })
      );
}

So my question boils down to either of these two things:

  1. How can I make sure that I only call the re-tokenizer endpoint only once but still not continue to not be routed upon refresh?
  2. How can I make the BehaviorSubject work until I receive a user object?

I have checked the following links: AuthGuard router angular and What is the difference between Subject and BehaviorSubject?. However, upon using a Subject, I may need to reauthenticate the user every time I visit a guarded route which is counterintuitive.

  • You don’t really get a second change in your canLoad.. the first emission is the only thing. Does it help when you start (in your pipe, before switchMap) with a filter that specifically checks for null? – MikeOne Jun 14 '21 at 19:08
  • Doesn't the if statement on the SwitchMap work the same way? But if I were to apply a filter operator before SwitchMap, what would I do with it? – Viany Manuel Jun 15 '21 at 03:13

1 Answers1

0

Okay, so I've solved this problem. What I did was to create a new Subject at the AuthService. The guard checks if the BehaviorSubject inside the AuthService returns a null, and if it does, I will switchMap into using the Subject. Here is the code.

AuthGuard

canLoad() {
    return this.authServ.user.pipe(
        switchMap((user) => {
          if (!!user && !!user._token) {
            return of(user);
          }
          return this.authServ.initialLogin;
        }),
        map((user) => {
          if (!!user?._token) {
            return true;
          }
          this.router.navigate(['/login']);
          return false;
        })
}

AuthService

user = new BehaviorSubject<User>(null as any);
initialLogin = new Subject<User>();
reauthenticate(): Observable<User> {
    // return HTTP call here and pipe the user data into user BehaviorSubject
    return this.http.get<User>(URL, {withCredentials: true}).pipe(tap((user) => {
        this.initialLogin.next(user);
        this.user.next(user);
    }))
}

AppComponent

ngOnInit(): void {
    this.authServ.reauthenticate().subscribe()
}

This way, I only authenticate once upon the start of the project