1

I'm attempting to add a Route Guard that will send a JWT to a PHP API that will return true or false depending on if the user is authenticated. Through my testing, the Route Guard works until it actually calls the API. If the API returns false, the guard works as expected. However, if the API returns true, then the guard seems to want to redirect the user as if it had returned false, but instead of displaying the home screen, it just displays a nothing.

auth.guard.ts

canActivate(): boolean {
    if (localStorage.getItem('portfolioJWT') === null) {
        this.router.navigate(['/']);
        return false;
    } else {
        const token = JSON.parse(localStorage.getItem('portfolioJWT'));

        this.authService.isAuth(token).subscribe(res => {
            console.log(res);
            if(!res) {
                this.router.navigate(['/']);
                console.log("NOT Authorized");
                return false;
            } else {
                console.log("Authorized");
                return true;
            }
        });
    }
}

auth.service.ts

isAuth(token: string): Observable<Boolean> {

  const authHttpOptions = {
      headers: new HttpHeaders({
          'Content-Type': 'application/x-www-form-urlencoded',
          'Authorization': 'Bearer ' + token
      })
  };

  return this.http.post<Boolean>('http://portfolioapi/api/checkAuth', {}, authHttpOptions);
}

I have the guard console logging the returned value, and whether or not the user is authorized, which it does and it shows the correct data.

Jeffrey Roosendaal
  • 6,872
  • 8
  • 37
  • 55
CaptainQuint
  • 332
  • 6
  • 22

1 Answers1

3

The problem may be that you are not using a Promise<boolean> for the canActivate, so while canActivate is still executing in the background, the router already moved on, thus triggering unexpected behaviour.

An example may be that the API returns false and initializes a navigate, but only after the router already navigated you somewhere (that may trigger the blank page). Same goes for the console.log(res). It may work, but it's already too late, the router has moved on.

What you want to achieve is that the routing should pause until is has received either true of false. Checking for a local variable's value may work fine without a Promise, but it does matter when performing an API call, because it is asynchronous, so you explicitly need the tell the router to wait for the call to finish.

canActivate(): Promise<boolean> {
    return new Promise((resolve) => {
        if (localStorage.getItem('portfolioJWT') === null) {
            this.router.navigate(['/']);
            resolve(false);
        } else {
            const token = JSON.parse(localStorage.getItem('portfolioJWT'));

            this.authService.isAuth(token).subscribe(res => {
                console.log(res);
                if(!res) {
                    this.router.navigate(['/']);
                    console.log("NOT Authorized");
                    resolve(false)
                } else {
                    console.log("Authorized");
                    resolve(true)
                }
            });
        }
    })
}
Jeffrey Roosendaal
  • 6,872
  • 8
  • 37
  • 55
  • You should not need a promise. Guards can (and often do) return true or false. It is actually recommended that you don't use promises in Angular except when absolutely necessary to communicate with an external API that requires them. Also, you don't need to unsubscribe from a subscription when working with `HttpClient`. – DeborahK Aug 15 '18 at 17:28
  • @DeborahK Since this was the solution to OP's problem, how would you've solved this without a Promise? Of course the Guard should return true or false here, but you need the Promise to tell the router to wait for the API call (async), right? About the subscription, I just read documentation on that part for HttpClient and Router, and you are right, they are cleaned up automatically. Answer edited. – Jeffrey Roosendaal Aug 16 '18 at 13:08
  • Observables are the recommended alternative to promises for async operations. – DeborahK Aug 16 '18 at 15:12