Back to Home

Updating JWT in Dio: Interceptor approach

The article covers JWT token refresh mechanisms in Dio: reactive Interceptor with Completer, proactive QueryInterceptor by exp. Comparison of Basic/Bearer schemes, dual-token architecture, code examples for production.

Auto-refreshing Dio tokens: from theory to code
Advertisement 728x90

Automatic JWT Token Refresh in Dio: Interceptor and QueryInterceptor

Servers verify authorization via the Authorization header in the format <auth-scheme> <auth-parameters>. Middleware processes this header for all protected endpoints. If data is missing or invalid, it returns 401 Unauthorized or 403 Forbidden.

Main schemes:

  • Basic: Base64-encoded login:password (Authorization: Basic bG9naW46cGFzc3dvcmQ=). Low security due to easy decoding.
  • Bearer: JWT token (Authorization: Bearer <Token>). Supports expiration, requires refresh.

JWT consists of Header (algorithm), Payload (user data, exp), Signature (server secret). Format: {Header}.{Payload}.{Signature} in Base64.

Google AdInline article slot

JWT Structure and Refresh

Payload contains exp (expiration time). On 401, clients request a refresh via a protected endpoint without middleware.

Dual-token systems:

  • Access Token: Short-lived (minutes), for API requests.
  • Refresh Token: Long-lived, for generating new Access tokens.

Strategies for Handling Expired Tokens

Logout (Simplest Approach)

On 401 — logout. Implementation is trivial but inconvenient for users.

Google AdInline article slot

Pros:

  • Minimal code.

Cons:

  • Frequent re-logins.
  • Incompatible with dual-token.

Refresh on Error (Interceptor)

Implemented via Interceptor in Dio. Adds token in onRequest, handles 401 in onError.

Google AdInline article slot
class TokenInterceptor extends Interceptor {
  final TokenStorage _storage;

  TokenInterceptor(this._storage);

  @override
  Future<void> onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
    final token = await _storage.getToken();
    if (token != null) {
      options.headers['Authorization'] = 'Bearer $token';
    }
    handler.next(options);
  }
}

Setup:

final dio = Dio()..interceptors.add(TokenInterceptor(TokenStorage()));

In onError for 401:

  • Check _isRefreshing flag.
  • If not — initiate refresh via a separate Dio.
  • Queue requests wait via Completer<void> _refreshCompleter.
Future<String?> _updateToken() async {
  final oldToken = await _storage.getToken();
  try {
    final dio = Dio();
    final response = await dio.get('/auth/refresh/', headers: {'Authorization': 'Bearer $oldToken'});
    final newToken = (response.data as Map<String, dynamic>)['token'];
    await _storage.setNewToken(newToken);
    return newToken;
  } catch (e) {
    await _storage.clearToken();
    return null;
  }
}

Full onError logic synchronizes parallel requests, retries with new token.

Advantages:

  • Works with dual-token.
  • No external libraries.

Disadvantages:

  • Wave load on server with mass retry.
  • Response delay (error + refresh + retry).

Proactive Refresh (QueryInterceptor)

Uses exp from JWT to refresh before expiration. QueryInterceptor ensures sequential request processing.

Parse JWT to extract exp:

  • Decode Base64 Payload.
  • Parse JSON, get timestamp.

Advantage: no blocking 401 errors. Disadvantage: strict queue, parallel requests are blocked.

Key Takeaways

  • Use Interceptor for reactive refresh with Completer for synchronization.
  • QueryInterceptor suits proactive refresh based on exp.
  • Store tokens in isolated storage, use a separate Dio for refresh.
  • Handle race conditions with multiple 401s.
  • Dual-token enhances security: short access + refresh.

— Editorial Team

Advertisement 728x90

Read Next