Master RxJs: Handling auth-related events frontend with RxJs
By: Lars Holdaas
Tags: Rxjs, Angular, Front End Development, Reactive Programming

Photo by Eric Prouzet on Unsplash

RxJs excels at handling asynchronous operations. Today, we’re going to look at how to solve one of the more challenging parts of front-end development using RxJs: Handling events related to authentication.

So how much ground are we going to cover with this article? We’re going to look at the following common problems related to authentication handling:

Fetching and storing tokens in a BehaviorSubject in a service

Automatic notification to the user if their token is about to expire

Automatic logout when the user is inactive for a given period of time

Note that this article does NOT cover topics of actual route restrictions concerning tokens, or authentication processes for API. The former is too framework-oriented and does not necessarily relate directly to RxJs, and the latter is strictly API-related, whereas I’m looking at using RxJs to improve the frontend developer and user experience.

As the code required to showcase this usage of RxJs is quite extensive, instead of the usual code-along I’ll simply present a finished solution and explain how each part works. The finished example is available here:

Handling auth-related problems in frontend using RxJs

Let’s understand how this work by going through one file at the time. We are going to skip looking at files that are not necessarily relevant to what we’re trying to achieve. For example, dashboard and landing-page are simple pages with a button and do not deserve further inspection.

Let’s start by looking at the services.

app/services/auth.service.ts

auth.service.ts is the service that accepts requests to log in or out, stores the auth token and exposes data related to the token.

Close to the top we see a declaration for authToken$ as a BehaviorSubject of type Token . If you’re unfamiliar with BehaviorSubject , reading up on them might be smart, but in this case just consider them the go-to type for storing data in services.

Next we have a set of selector functions that return data based on the state of authToken$ . I prefer having the state-holding BehaviorSubject kept private inside a service and rather expose observable selectors that return either props directly from it or calculated variables based on the data stored. We expose three observable selectors: isAuthenticated$ which returns true when we have a token, userData$ which exposes the userData stored in the token, and exp$ which exposes the expiry (timestamp) of the token.

We also want auth.service to be solely responsible for logging out the user when the token expires. Although this could be done in auth-timeout-warning.service described below, considering that it handles situations when the token is close to expiry, forcing a log out when the token expires is a concern that should not be separated from auth.service.ts .

We implement this by making a forceLogout$ Observable that pipes exp$ and creates a timer that waits until the seconds between and now the expiry has passed. We destroy this pipe if the user already logs out, and otherwise subscribes to it and forces a logout when it emits.

AuthService has two methods: logIn() and logOut() . Although this varies from developer to developer, I prefer NOT to return observables from service actions, but rather let components or other service react to changes in the service itself once it has finishes running any function. Therefore, logIn and logOut are both void functions.

Using timer(400) we emulate a delay for the login which we will use for testing the case where the user logs in JUST before he is automatically logged out.

Other than that, both logIn and logOut basically behaves by updating the authToken$ state. The expiry of the token is set to 4 seconds from whenever logIn is called.

/services/auth-timeout-warning.service.ts

auth-timeout-warning.service.ts is responsible for letting the UI know when a token is about to expire, and also to inform the UI of when the token was reset meaning a warning is no longer necessary to display.

From the top we see the calculateDelayTime method. This is used when a new token is fetched, and will return how much time is left until 2 seconds before the user is automatically logged out.

The service exposes two variables: timeoutWasReset$ which is a Subject that notifies whenever the expiry time has been updated and timeoutNotificationReached$ which is an Observable that emits whenever we are 2 seconds away from being automatically logged out.

Next comes the first tricky part of this setup. We define timeOutNotificationReached$ to be a reaction to authService.exp$ . We immediately signal that the timeout has been reset by calling this.timeoutWasReset$.next() . Then, we check if the expiry date is actually set ( authService.exp$ will emit null when the user logs out), and if it is we let the emission pass.

We then switchMap over to a pipe we create by calling of(exp) (our final emission will be the actual expiration time). We delay it by 2 seconds less than the expiration time, and then say we only care about waiting for the emission as long as the user is logged in by using takeUntil on exp$ and filtering out anything truthy.

The last step might not make a lot of sense at first, but remember that before we use switchMap we already do the opposite: We filter out falsey exp values. In this case, a new emission will reach switchMap , and since switchMap automatically unsubscribes from the inner observable and creates a new instance when an emission reaches it, we have already covered the case where a new valid expiration date is emitted. In the case where exp$ emits null , we know the user has logged out, so we only bother unsubscribing the inner Observable. (We don’t do this on the outer Observable as this would make the stream never accept any emissions from authService.exp$ until the page is reloaded.)

Finally, we add shareReplay to ensure that only one of these pipes are active no matter the number the subscribers, so that we for instance avoid side effects like timeoutWasReset$ being called when we get new subscribers rather than only when authService.exp$ emits..

So far, so good? The pipe is a bit complicated, but summing it up all it really does is the following: