Skip to content

iamdipanshusingh/dio_refresh

Repository files navigation

Dio Refresh Interceptor

pub package license

A Dart package that provides an interceptor for handling automatic token refresh in Dio HTTP client requests. It simplifies the process of managing access and refresh tokens, ensuring that your API requests stay authenticated, even when the access token expires.

Features

  • Automatic Token Refresh: Automatically refreshes the access token when it expires using a custom refresh callback.
  • Customizable: Define custom logic for determining when a token refresh is needed and how headers are generated.
  • Singleton Token Manager: A singleton TokenManager class for easy token storage and retrieval.
  • Seamless Integration: Designed for use with the Dio HTTP client package.

Installation

Add the following dependency to your pubspec.yaml file:

dependencies:
  dio: ^5.0.0
  dio_refresh: ^1.0.0
  flutter:
    sdk: flutter

Then, run:

flutter pub get

Getting Started

Setup DioRefreshInterceptor

To use the DioRefreshInterceptor, you'll need to define the following callbacks:

  • OnRefreshCallback: Handles the logic for refreshing the access token.
  • ShouldRefreshCallback: Determines whether a response requires a token refresh.
  • TokenHeaderCallback: Generates headers with the access token.

Example

import 'package:dio/dio.dart';
import 'package:dio_refresh/dio_refresh.dart';

void main() {
  final dio = Dio();

  // Define the TokenManager instance.
  final tokenManager = TokenManager.instance;
  tokenManager.setToken(
    TokenStore(
      accessToken: authToken,
      refreshToken: refreshToken,
    ),
  );

  // Add the DioRefreshInterceptor.
  dio.interceptors.add(DioRefreshInterceptor(
    tokenManager: tokenManager,
    authHeader: (tokenStore) {
      if (tokenStore.accessToken == null) {
        return {};
      }
      return {
        'Authorization': 'Bearer ${tokenStore.accessToken}',
      };
    },
    shouldRefresh: (response) =>
        response?.statusCode == 401 || response?.statusCode == 403,
    onRefresh: (dio, tokenStore) async {
      final response = await dio.post('/refresh', data: {
        'refresh_token': tokenStore.refreshToken,
      });
      return TokenStore(
        accessToken: response.data['accessToken'],
        refreshToken: response.data['refreshToken'],
      );
    },
  ));
}

TokenManager Usage

The TokenManager class is used to manage your access and refresh tokens.

// Retrieve the singleton instance of TokenManager.
final tokenManager = TokenManager.instance;

// Set new tokens after refreshing.
tokenManager.setToken(TokenStore(
  accessToken: 'newAccessToken',
  refreshToken: 'newRefreshToken',
));

// Access the current access token.
print(tokenManager.accessToken);

API Reference

DioRefreshInterceptor

  • DioRefreshInterceptor: A custom interceptor for handling token refresh logic.
    • tokenManager: Instance of TokenManager to manage tokens.
    • authHeader: Callback to generate authorization headers.
    • shouldRefresh: Callback to determine if a refresh is needed.
    • onRefresh: Callback to handle the refresh logic and return a new TokenStore.
    • isTokenValid: Optional callback to validate if a token is still valid.
    • retryInterceptors: Optional list of interceptors to be added to the Dio instance used for retrying requests. This is useful for adding logging or other custom interceptors to the retry mechanism. Note: Do not add another DioRefreshInterceptor to this list, as it may cause an infinite loop.

TokenManager

  • TokenManager: A singleton class to manage tokens.
    • setToken(TokenStore tokenStore): Updates the stored access and refresh tokens.
    • accessToken: Returns the current access token.
    • refreshToken: Returns the current refresh token.
    • isRefreshing: A ValueNotifier that indicates whether a refresh is in progress.

typedef Callbacks

  • OnRefreshCallback: Future<TokenStore> Function(Dio, TokenStore)
    • Handles the token refresh logic.
  • ShouldRefreshCallback: bool Function(Response?)
    • Determines if a token refresh is required.
  • TokenHeaderCallback: Map<String, String> Function(TokenStore)
    • Generates authorization headers for requests.
  • IsTokenValidCallback: bool Function(String)
    • Validates if a token is still valid.
    • Default implementation checks if the JWT token is not expired.
    • Called if [shouldRefresh] returns true.
    • Can be customized to implement different token validation strategies.

Example with Custom Token Validation

final dio = Dio();
dio.interceptors.add(DioRefreshInterceptor(
  tokenManager: tokenManager,
  onRefresh: onRefresh,
  shouldRefresh: shouldRefresh,
  authHeader: authHeader,
  isTokenValid: (token) {
    // Implement custom token validation logic
    try {
      final decodedToken = JwtDecoder.decode(token);
      // Add additional validation checks here
      return !JwtDecoder.isExpired(token) && 
             decodedToken['custom_claim'] == 'expected_value';
    } catch (_) {
      return false;
    }
  },
));

Example with retryInterceptors

You can provide a list of custom interceptors that will be added to the Dio instance responsible for retrying the request after a successful token refresh. This is particularly useful for logging the retry attempts.

Important: Do not add an instance of DioRefreshInterceptor itself to the retryInterceptors list. Doing so may create an infinite loop if the token refresh request also fails. The constructor includes an assertion to prevent this in debug mode.

First, create a logger interceptor. For example, a simple CustomLogInterceptor:

import 'package:dio/dio.dart';

class CustomLogInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    print('Retrying request to: ${options.uri}');
    super.onRequest(options, handler);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    print('Retry successful with status code: ${response.statusCode}');
    super.onResponse(response, handler);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    print('Retry failed with error: $err');
    super.onError(err, handler);
  }
}

Then, pass an instance of this interceptor to DioRefreshInterceptor:

final dio = Dio();
dio.interceptors.add(DioRefreshInterceptor(
  tokenManager: tokenManager,
  onRefresh: onRefresh,
  shouldRefresh: shouldRefresh,
  authHeader: authHeader,
  retryInterceptors: [CustomLogInterceptor()],
));

Contributing

Contributions are welcome! Feel free to open issues or submit a pull request on GitHub. For significant changes, please open an issue first to discuss what you would like to change.

About

A flutter package for intercepting requests and automatically fetching refresh tokens on API failures

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Contributors 5