Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save duytq94/7f23f0b391a3d79e641e15f586187cb3 to your computer and use it in GitHub Desktop.
Save duytq94/7f23f0b391a3d79e641e15f586187cb3 to your computer and use it in GitHub Desktop.
class AuthInterceptor extends InterceptorsWrapper {
final Dio dio;
AuthInterceptor(this.dio);
// when accessToken is expired & having multiple requests call
// this variable to lock others request to make sure only trigger call refresh token 01 times
// to prevent duplicate refresh call
bool _isRefreshing = false;
// when having multiple requests call at the same time, you need to store them in a list
// then loop this list to retry every request later, after call refresh token success
final _requestsNeedRetry = <({RequestOptions options, ErrorInterceptorHandler handler})>[];
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
final accessToken = getAccessTokenFromLocalStorage();
options.headers['authorization'] = 'Bearer $accessToken';
return handler.next(options);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
final response = err.response;
if (response != null &&
// status code for unauthorized usually 401
response.statusCode == 401 &&
// refresh token call maybe fail by it self
// eg: when refreshToken also is expired -> can't get new accessToken
// usually server also return 401 unauthorized for this case
// need to exlude it to prevent loop infinite call
response.requestOptions.path != "path/your/endpoint/refresh") {
// if hasn't not refreshing yet, let's start it
if (!_isRefreshing) {
_isRefreshing = true;
// add request (requestOptions and handler) to queue and wait to retry later
_requestsNeedRetry.add((options: response.requestOptions, handler: handler));
// call api refresh token
final isRefreshSuccess = await _refreshToken();
if (isRefreshSuccess) {
// refresh success, loop requests need retry
for (var requestNeedRetry in _requestsNeedRetry) {
// don't need set new accessToken to header here, because these retry
// will go through onRequest callback above (where new accessToken will be set to header)
// won't use await because this loop will take longer -> maybe throw: Unhandled Exception: Concurrent modification during iteration
// because method _requestsNeedRetry.add() is called at the same time
// final response = await dio.fetch(requestNeedRetry.options);
// requestNeedRetry.handler.resolve(response);
dio.fetch(requestNeedRetry.options).then((response) {
requestNeedRetry.handler.resolve(response);
}).catchError((_) {});
}
_requestsNeedRetry.clear();
_isRefreshing = false;
} else {
_requestsNeedRetry.clear();
// if refresh fail, force logout user here
}
} else {
// if refresh flow is processing, add this request to queue and wait to retry later
_requestsNeedRetry.add((options: response.requestOptions, handler: handler));
}
} else {
// ignore other error is not unauthorized
return handler.next(err);
}
}
Future<bool> _refreshToken() async {
try {
final refreshToken = getRefreshTokenFromLocalStorage();
final res = await callApiRefreshToken(refreshToken);
if (res.response.statusCode == 200) {
print("refresh token success");
final refreshResponse = RefreshResponse.fromJson(res.data);
// save new access + refresh token to your local storage for using later
setAccessTokenToLocalStorage(refreshResponse.accessToken);
setRefreshTokenToLocalStorage(refreshResponse.refreshToken);
return true;
} else {
print("refresh token fail ${res.response.statusMessage ?? res.response.toString()}");
return false;
}
} catch (error) {
print("refresh token fail $error");
return false;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment