'renew token in signalR after expired token

I create a realtime connection via SignalR From client(angular 9) and server(asp.net core 3.1) and Authorize hub by JWT Token such as below code :

 private createConnection() {
      this.hubConnection = new HubConnectionBuilder().withUrl(`${this.appConfig.hubEndpoint}/Hubs`,
        { accessTokenFactory: () => jwtToken })
        .withAutomaticReconnect()
        .build();
  }

  private startConnection(): void {
    this.hubConnection
      .start()
      .then(() => {
        this.connectionIsEstablished = true;
        this.connectionEstablished.emit(true);
      })
      .catch(err => {
        console.log('Error while establishing connection, retrying...');
      });
  }

this works fine until the token expired. According to my research, after receiving the new token with the refresh token, the previous connection should be stopped and a new connection should be created with the new token. Now I want to know how should I do this? Do I have to constantly check the token? Or should this be addressed by sending each request to the server?



Solution 1:[1]

The solution I came up with is to intercept the auth calls of the signalR client by extending the signalR.DefaultHttpClient it uses. If there is a 401 then I refresh the token (via my authService), and retry the call:

Typescript:

const getAuthHeaders = () => {
  return {
    Authorization: `Bearer ${authService.getToken()?.accessToken}`,
  };
};

class CustomHttpClient extends signalR.DefaultHttpClient {
  constructor() {
    super(console); // the base class wants a signalR.ILogger
  }
  public async send(
    request: signalR.HttpRequest
  ): Promise<signalR.HttpResponse> {
    const authHeaders = getAuthHeaders();
    request.headers = { ...request.headers, ...authHeaders };

    try {
      const response = await super.send(request);
      return response;
    } catch (er) {
      if (er instanceof signalR.HttpError) {
        const error = er as signalR.HttpError;
        if (error.statusCode == 401) {
          //token expired - trying a refresh via refresh token
          await authService.refresh();
          const authHeaders = getAuthHeaders();
          request.headers = { ...request.headers, ...authHeaders };
        }
      } else {
        throw er;
      }
    }
    //re try the request
    return super.send(request);
  }
}

const connection = new signalR.HubConnectionBuilder()
  .withUrl("/MyHub", {
// use the custom client
    httpClient: new CustomHttpClient(),
  })
  .configureLogging(signalR.LogLevel.Information)
  .build();

see the options of .withUrl(..) here: https://docs.microsoft.com/en-us/aspnet/core/signalr/configuration?view=aspnetcore-6.0&tabs=dotnet

Solution 2:[2]

When the token will expire the connection will be droped by the server and you will have the error on the server side. I believe is the 405 error code Method now allowed that you will get.

So what you need is catch this token expiration error and drop the connection so you can start a new one with the new token.

Solution 3:[3]

What did work for me, it's a QUICK AND DIRTY fix, was to reload the page on close events:

this.hubConnection.onclose(() =>{
  window.location.reload()
})

Explanation

I am wrapping the connection process with RxJS, so a better fix for my case is to throw an error instead of reloading the page and catch it with retryWhen operator. But as this is a hard bug (need to wait 1h for the token to expire, and locally the emulator doesnt care about tokens...), I just prefered to go with this temporary solution.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Kiril1512
Solution 3 Mehdi Benmoha