Token refresh concurrency issue in Dart with ASP.NET Core WebAPI

1,613

I faced similar problem and tried to solve it using below approach.

I use flutter-redux to manage the state on client side.

  1. Get jwt token after login
  2. Decode the jwt token on client side as responded from server.
  3. It contains a timeout - time of expiration.
  4. Create a redux middleware on client side lets say _createRefreshTokenMiddleware.
  5. Every request from client should go through this middle-ware before sending to server.
  6. In this middle-ware, with every requests to server, check the token timeout, if token is expired, hold those request, send request to server to refresh token, wait until received new token, use this new token to send those request to server.
  7. All other requests for which token will expire will wait on a common promise, lets say refreshTokenPromise to get refreshToken get resolved first. In this way you don't have to send multiple refreshToken requests.
  8. If the token is still valid, let the requests to go through.

Refer below example -

Your middleware :

Middleware<AppState> _createRefreshTokenMiddleware() {
  return (Store store, action, NextDispatcher next) async {
    AppState appState = store.state;
    AuthState auth =  appState.auth;

    if (isTokenExpired(auth)) {
      if (auth.refreshTokenPromise == null) {
        refreshToken(store).then((res) => next(action));
      } else {
        auth.refreshTokenPromise.then((res) => next(action));
      }
    }
    next(action);
  };
}

All the requests for which token is expired will wait on refreshTokenPromise to get resolved and as soon as that is resolved all of the pending requests will have new updated token set in request header (e.g).

Checking for token expiration :

bool isTokenExpired(AuthState auth) {
  int bufferSeconds = 10;
  if(auth != null && auth.authTokens != null && auth.authTokens.tokenExpiryTime != null) {
    var currentTime = DateTime.now();
    Duration durationRemaining = auth.authTokens.tokenExpiryTime.difference(currentTime);

    return (durationRemaining.inSeconds - bufferSeconds) <= 0 ? true : false;

  }
  return false;
}

You send the request to refresh token 10 seconds before it is actually expired.

AuthState Model:

@immutable class AuthState {

// properties
final bool isAuthenticated;
final bool isAuthenticating;
final AuthTokens authTokens;
final String error;
final Future<dynamic> refreshTokenPromise;

// constructor with default
AuthState({
    this.isAuthenticated = false,
    this.isAuthenticating = false,
    this.authTokens,
    this.error,
    this.refreshTokenPromise,
});

}

Your auth-state model can be like above.

AuthToken:

@immutable
class AuthTokens {

  // properties
  final String accessToken;
  final String refreshToken;
  final DateTime tokenExpiryTime;

  // constructor with default
  AuthTokens({
    this.accessToken,
    this.refreshToken,
    this.tokenExpiryTime,
  });
 }

Although I have given redux based solution here but same strategy can be applied anywhere else as well. I hope it helps.

Share:
1,613
tylkonachwile
Author by

tylkonachwile

Updated on December 07, 2022

Comments

  • tylkonachwile
    tylkonachwile over 1 year

    I wrote a simple application in Flutter using Dart. I use JWT tokens to authenticate user. Primary token is valid only 60 seconds. When user send a request with expired token the webapi returns 401. Then in my Dart code I check if statuscode of response is 401 If yes, then I send a request to RefreshToken endpoint and send request one more time (this request which returned 401 earlier).

    If user does many actions too fast, expired token is renewed many times. I'd like to avoid this. In perfect soultion when token is being refreshing, other requests should wait.