JWT Guard State Issue

Because it’s unsupported by the language itself, asynchronous JavaScript is extremely prone to race conditions, and I suspect you may have one here.

One way I try to avoid them is to minimize the number of external stuff that asynchronous code references, and to try to prove as rigorously as I can that the external stuff they do need is in the state that they need it to be when they are called.

So I am concerned that the token may not be coming out of storage before you try to use it.

I gather from authenticationState.value that authenticationState is a BehaviorSubject. There’s nothing wrong with that per se, but part of the contract with BehaviorSubject's is that you have to actually be OK with them in their initial state - you can’t cheat by initializing it to something you don’t ever actually want to see. You also have to take extra care when typing them. It’s probably a boolean now, but that’s not entirely accurate. There are really three states: “authenticated”, “not authenticated”, and “hang on, I don’t know yet”.

There are a bunch of ways to handle this; here are two:

  1. Ditch the guard and have a dedicated listener interact with the router proactively, for example in app.component.ts:
this.authService.authenticationState.subscribe(authed => {
  if (!authed) {
    this.router.navigate(["/auth"]);
  }
});
  1. Don’t expose authenticationState directly until you’re certain it’s ready:
private jwt$$: Promise<BehaviorSubject<string>>;

constructor(private storage: Storage) {
  this.jwt$$ = storage.ready()
    .then(() => this.storage.get(environment.TOKEN_KEY))
    .then(jwt => new BehaviorSubject<string>(jwt));
}

watchAuthenticationToken(): Promise<Observable<string>> {
  return this.jwt$$;
}

peekAuthenticationState(): Promise<boolean> {
  return this.jwt$$.then(jwt$ => !!jwt$.value);
}

// implicitly assumes storage is ready
pokeAuthenticationToken(jwt: string): void {
  this.jwt$$.then(jwt$ => {
    jwt$.next(jwt);
    this.storage.set(environment.TOKEN_KEY, jwt);
  });
}
canLoad(): Promise<boolean> {
  return this.authService.peekAuthenticationState();
}