First of all, let me start by saying that I am using Angular 10 with the Nebular UI Library for the front-end, Node.js for the back-end API, and JWT with the email/password strategy for authentication. I have noticed that for every time the user sings-in and signs back out without refreshing the application, a new duplicate sign-out request is made to the server (multiple http requests are being sent out). If you refresh the application after you sign back out though, the problem goes away. I'm not sure if I'm skipping something or I'm simply ignorant on the right way to log out and sign back in using JWTs, but I've been trying to find a solution to this problem for days now with no success so I'm eager for some help.
Current behavior:
If the user were to sign in and logs back out again more than once, the sign-out request made to the server is duplicated. This issue persists REGARDLESS of if you use an http interceptor (NbAuthJWTInterceptor or otherwise).
Expected behavior:
If the user were to sign in and log back out again, there should be NO redundant sign-out requests made to the server regardless of how many times the user repeats these steps without refreshing the app.
Steps to reproduce:
Here is a screenshot from my dev-tools network tab for these 4 steps (after signing-in and signing back out 4 times):
Related code: On the client side I have the header.component.ts file from which the sign out process is initiated:
...
ngOnInit() {
// Context Menu Event Handler.
this.menuService.onItemClick().pipe(
filter(({ tag }) => tag === 'my-context-menu'),
map(({ item: { title } }) => title),
).subscribe((title) => {
// Check if the Logout menu item was clicked.
if (title == 'Log out') {
// Logout the user.
this.authService.logout('email').subscribe(() => {
// Clear the token.
this.tokenService.clear()
// Navigate to the login page.
return this.router.navigate([`/auth/login`]);
});
}
if (title == 'Profile') {
return this.router.navigate([`/pages/profile/${this.user["_id"]}`]);
}
});
}
...
On the server side, there is the sign-out API route that returns a successful 200 response:
// Asynchronous POST request to logout the user.
router.post('/sign-out', async (req, res) => {
return res.status(200).send();
});
You’re subscribing inside of another subscription. This causes another subscription to be made each time this.menuService.onItemClick()
is called.
You need to use a flattening strategy by using the proper Rxjs operator (exhaustMap, concatMap, switchMap, mergeMap).
In your case I would refactor like this (don’t forget to unsubscribe to each subscription in ngOnDestroy)
const titleChange$ = this.menuService.onItemClick()
.pipe(
filter(({ tag }) => tag === 'my-context-menu'),
map(({ item: { title } }) => title)
);
this.logOutSubscription = titleChange$.pipe(
filter((title) => title == 'Log out'),
exhaustMap((title) => this.authService.logout('email')),
tap(() => {
this.tokenService.clear()
this.router.navigate([`/auth/login`]);
})
.subscribe();
this.profileNavSubscription = titleChange$
.pipe(
filter((title) => title == 'Profile'),
tap(title => {
this.router.navigate([`/pages/profile/${this.user["_id"]}`])
})
.subscribe();
`