Nick Hodges
Nick Hodges

Reputation: 17138

Why to I have a "one-time, first-time" error on my isLoggedIn observable?

I know this is hard to believe, but Observables are kind of tricky and I don't 100% get them. :-)

Given this code:

  private currentUserSubject: BehaviorSubject<User>;
  public currentUser: Observable<User>;
  private loggedIn = new BehaviorSubject<boolean>(this.tokenIsValid());

  ...

  get isLoggedIn(): Observable<boolean> {
    this.loggedIn.next(this.tokenIsValid());
    return this.loggedIn.asObservable();
  }

  private tokenIsValid(): boolean {
    let currentUser: User = this.currentUserValue;
    if (currentUser) {
      const token = currentUser.token;
      const tokenInfo = this.getDecodedAccessToken(token);
      if (Date.now() <= tokenInfo.exp * 1000) {
        return true;
      }
    }
    return false;
  }

  private getDecodedAccessToken(token: string): any {
    try {
      return jwt_decode(token);
    } catch (Error) {
      return null;
    }
  }

Why does isLoggedIn report true when it shouldn't only one time (the first time after logout), and then work perfectly thereafter?

Here's the sequence:

  1. I login in with JWT with nice, testable 15 second time out.
  2. I go to a protected resource and see it.
  3. I wait way more 15 seconds for the JWT to expire
  4. I go to another protected resource, and I can see it.
  5. I go back to the initial protected resource, and I can't see it.
  6. I go back yet again to the second protected resource, and I can't see it.

And by "see it", I mean I am not prohibited by my AuthGuard to see the page.

My question then is why the error only the first time?

ADDED: Here's the constructor for the AuthenticationService class, if that helps:

constructor(private httpClient: HttpClient) {
    this.currentUserSubject = new BehaviorSubject<User>(JSON.parse(localStorage.getItem(this.currUser)));
    this.currentUser = this.currentUserSubject.asObservable();
  }

Upvotes: 0

Views: 93

Answers (1)

Picci
Picci

Reputation: 17762

I do not understand the full context of your question but I see a potential problem in this piece of code

  get isLoggedIn(): Observable<boolean> {
    this.loggedIn.next(this.tokenIsValid());
    return this.loggedIn.asObservable();
  }

Here it seems you are trying to do 2 things

  1. First you notify if the user is logged in with this.loggedIn.next(this.tokenIsValid())
  2. Then you return the Subject (as an Observable) used to broadcast to all interested party the above notification

The point is that every party (e.g. your AuthGuard) which wants to be notified when something happens first has to subscribe to the Observable it is interested in, and only then it gets any notification when the Observable emits.

This means that you have to find a way for your interested party to first subscribe to loggedIn as Observable with something like

  get isLoggedIn(): Observable<boolean> {
    return this.loggedIn.asObservable();
  }

and only then find the right place to make loggedIn emit something via this.loggedIn.asObservable().

If rather the point is that you want to receive a notification for an event even if it has happened before the subscription, you have to use ReplySubject and not BehaviorSubject.

Upvotes: 1

Related Questions