A new duplicate sign-out HTTP request is sent every time the user logs out using JWT authentication: Nebular, Angular, Node.js (Express)

  angular, express, jwt, nebular

Issue description

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.
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:

  1. The first time the user signs in everything works fine and there are no duplicate requests made to the server when you log out.
  2. After you sign back in for the 2nd time and sign out for the 2nd time without refreshing the application, the 2nd sign out request you make to the server will send out a duplicate sign-out request (2 identical sign-out requests are sent to the server).
  3. If the user signs in again for a 3rd time and signs back out for a 3rd time, then 3 sign-out requests will be sent to the server (a total of 3 identical requests sent out).
  4. If the user were to sign in and log back out again, the sign-out request would sent be duplicated one more time and a total of 4 identical sign-out requests would be sent out. This continues indefinitely.

Here is a screenshot from my dev-tools network tab for these 4 steps (after signing-in and signing back out 4 times):
duplicates

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 a sign-in API route that sends back the signed JWT:

// Asynchronous POST request to login user.
router.post('/sign-in', async (req, res) => {
    ...
    try{
         // Get the user from the DB.
        const user = await User.findOne({email: req.body.email});  
        
         // Create and assign a JWT token. 
        const accessToken = jwt.sign(
           {_id: user._id, role: user.role },
           ACCESS_TOKEN_SECRET,
           {expiresIn: '1m'}
         );

        // Send the JWT back as the response.
        res.send({token: accessToken});
        return res.status(200).send(); 
    }catch(err){
        return res.status(400).send(err);
    }
});

and there is also a 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();
});

Finally, here is my core.module.ts, where the NbAuthModule strategy is configured:

export const NB_CORE_PROVIDERS = [


  ...NbAuthModule.forRoot({

    strategies: [
      NbPasswordAuthStrategy.setup({
        name: 'email',

        token: {
          class: NbAuthJWTToken,
          key: 'token', // this parameter tells Nebular where to look for the token
        },


        // refreshToken: false, // No built-in refresh token strategy

        baseEndpoint: 'http://localhost:3000/api',
        login: {
          redirect: {
            success: '/',
            failure: null, // stay on the same page
          },
          endpoint: '/auth/sign-in',
          method: 'post',
        },
        register: {
          redirect: {
            success: '/',
            failure: null, // stay on the same page
          },
          endpoint: '/auth/sign-up',
          method: 'post',
        },

        logout: {
          redirect: {
            // Take the user back to the login page.
            success: '/auth/login',
            failure: '/auth/login',
          },
          endpoint: '/auth/sign-out',
          method: 'post',
          requireValidToken:true
        },
      }),
    ],
    forms: {
      login: {
        redirectDelay: 0,
        showMessages: {
          success: true,
        },
      },
      register: {
        redirectDelay: 0,
        showMessages: {
          success: true,
        },
      },
      logout: {
        redirectDelay: 0,
      },
    }
  }).providers,

  LayoutService,
];

Source: Angular Questions

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.