JWT Guard State Issue

Good Day,
I have no clue why this issue is happening. Can someone please help explain and show me how to fix?

Scenario:
I am getting a JWT web token successfully and I am storing it in storage.
Now I am trying to look in storage on app load to see if there is a valid token, if not then go to Login page, else continue.
Seemed simple… but I am missing something.

Now the code:

first, the function that checks the token in my auth service:

validateAuth() {

    this.storage.get(environment.TOKEN_KEY).then((val) => {
      if (val) {
        console.log('TOKEN: ', val);
        this.authenticationState.next(true);
        console.log('STATE 2: ', this.authenticationState.value);
      } else {
        console.log('TOKEN NOT SET');
        this.authenticationState.next(false);
      }
    });
  }

next I call the function in app.component.ts

initializeApp() {
    this.platform.ready().then(() => {
      this.statusBar.styleDefault();
      this.splashScreen.hide();
      this.authService.validateAuth();
    });
  }

then here is my guard:

canLoad(
    route: Route,
    segments: UrlSegment[]): Observable<boolean> | Promise<boolean> | boolean {
      if (!this.authService.authenticationState.value) {
        this.router.navigateByUrl('/auth');
      }

      return this.authService.authenticationState.value;
      // return false;
  }

and last code, I do a call in my auth.page.ts to try and verify whats going on:

ngOnInit() {

    console.log('STATE 1: ', this.authService.authenticationState.value);
}

here is my token in app:

so the token is there and then finally here is the console screenshot I get:

Can anyone please help me see my mistake and help me fix it?

Thank you so much in advance!

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

Thank you very much! you have given me much to think about.

One question though, is it not better in terms of coding standards / security to always make use of guards?

And how about Secure Storage for storing tokens instead the classic ones?

@rapropos thank you very much. with your guidance I managed to resolve the issue.
i did end up still using the guards though.

Thank you very much!

I’m glad you found a resolution. To tie up a loose end:

Not in my opinion.

It can be tough to talk about security, because it encompasses so many things, and as the cliche goes, any chain is only as strong as its weakest link. I tend to think in terms of two questions:

  • who is the blackhat? answers like “a rando with a copy of the app binary”, “a rogue registered user”, &c
  • what are we concerned about the blackhat doing? answers like “accessing features that are supposed to be paid-only in a demo app”, “downloading all our secret data”, or “impersonating an admin”

So for any situation where route guards might be relevant, the answers to those would seem to be:

  • blackhat is user of app on device under their physical control
  • we’re worried about them accessing pages of the app that they aren’t supposed to

I do not believe it is possible to guard against that sort of threat in an Ionic app, period, due to the nature of the runtime environment. Therefore, there’s no point in worrying about it. If you’re distributing an app that has guest pages, registered user pages, and admin pages, the overall security of your system cannot depend on route guards to keep blackhats out of the admin pages. What you can do is to design the app so that even if they get there, the worst thing that could happen is that they send a request to your backend that gets rejected. The actual security has to be done on servers, not on devices that your users control.

Route guards are just about presentation.

Thank you very much!