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:
- 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"]);
}
});
- 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();
}