dev3 ·
My API returns 401 when the access token expires. I want Dio to refresh it once, retry the original request and not fire ten refresh calls when ten requests fail together. How do I structure the interceptor?
0
DEV3 Developer Forum
Every post is reviewed first. Usually reviewed within 24 hours.
My API returns 401 when the access token expires. I want Dio to refresh it once, retry the original request and not fire ten refresh calls when ten requests fail together. How do I structure the interceptor?
Approach
Use an InterceptorsWrapper (or QueuedInterceptor) that attaches the token on request and, on a 401, refreshes once and replays the request. A QueuedInterceptor processes errors one at a time, which is the simplest way to stop ten simultaneous refreshes.
class AuthInterceptor extends QueuedInterceptor {
AuthInterceptor(this.dio, this.tokens);
final Dio dio; // the main client
final TokenStore tokens;
@override
void onRequest(RequestOptions o, RequestInterceptorHandler h) {
final t = tokens.access;
if (t != null) o.headers['Authorization'] = 'Bearer $t';
h.next(o);
}
@override
Future<void> onError(DioException e, ErrorInterceptorHandler h) async {
final isUnauthorized = e.response?.statusCode == 401;
final alreadyRetried = e.requestOptions.extra['retried'] == true;
if (!isUnauthorized || alreadyRetried) return h.next(e);
try {
// use a SEPARATE Dio (no interceptors) for the refresh and the replay
final plain = Dio(BaseOptions(baseUrl: dio.options.baseUrl));
final fresh = await plain.post(
'/auth/refresh',
data: {'refresh_token': tokens.refresh},
);
await tokens.save(fresh.data['access_token'], fresh.data['refresh_token']);
final req = e.requestOptions
..extra['retried'] = true
..headers['Authorization'] = 'Bearer ${tokens.access}';
h.resolve(await plain.fetch(req));
} on DioException {
await tokens.clear(); // refresh failed: force logout
h.next(e);
}
}
}
Key points
dio.fetch from inside a QueuedInterceptor can deadlock: if the replayed request also fails, its error waits in the same queue behind the handler that is waiting for it.extra['retried']) so a second 401 does not loop.tokens.access; you can also skip the refresh if the stored token differs from the one used in the failed request.flutter_secure_storage, not SharedPreferences.