Skip to main content
  1. Posts/

Retry, log and refresh auth tokens with Dio

<time datetime="2022-11-16 00:00:00 &#43;0000 UTC">16 November 2022</time><span class="px-2 text-primary-500">&middot;</span><span title="Reading time">7 mins</span><span class="px-2 text-primary-500">&middot;</span> <span class="mb-[2px]"> <a href="https://github.com/cgutierr-zgz/cgutierr-zgz.github.io/edit/main/content/posts/refreshing-auth-tokens-with-dio/index.md" class="text-lg hover:text-primary-500" rel="noopener noreferrer" target="_blank" title=""><span class="relative inline-block align-text-bottom px-1 icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M490.3 40.4C512.2 62.27 512.2 97.73 490.3 119.6L460.3 149.7L362.3 51.72L392.4 21.66C414.3-.2135 449.7-.2135 471.6 21.66L490.3 40.4zM172.4 241.7L339.7 74.34L437.7 172.3L270.3 339.6C264.2 345.8 256.7 350.4 248.4 353.2L159.6 382.8C150.1 385.6 141.5 383.4 135 376.1C128.6 370.5 126.4 361 129.2 352.4L158.8 263.6C161.6 255.3 166.2 247.8 172.4 241.7V241.7zM192 63.1C209.7 63.1 224 78.33 224 95.1C224 113.7 209.7 127.1 192 127.1H96C78.33 127.1 64 142.3 64 159.1V416C64 433.7 78.33 448 96 448H352C369.7 448 384 433.7 384 416V319.1C384 302.3 398.3 287.1 416 287.1C433.7 287.1 448 302.3 448 319.1V416C448 469 405 512 352 512H96C42.98 512 0 469 0 416V159.1C0 106.1 42.98 63.1 96 63.1H192z"/></svg> </span> Suggest an edit</a> <a href="https://github.com/cgutierr-zgz/dio-intercetors-loggers" class="text-lg hover:text-primary-500" rel="noopener noreferrer" target="_blank"> Β· <span class="relative inline-block align-text-bottom px-1 icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg> </span> View source </a> </span>

dio by Flutterchina

Important Note: This post was made before wendux announced here he was going to stop maintaining the package.

Anyway, theres a hard fork of dio called diox that you can use to follow along with this post.
diox mantainers are now the original package mantainers, so you can use the original package and follow along with this post as if nothing happened.

I’ll try to make a new post using http if I have the time. πŸ˜…

In this post, I’ll show you how to use dio to make HTTP requests and how to use interceptors to refresh tokens and retry failed requests, everything while easily logging the requests and responses.
This post was made using:

What is dio? 🧐 #

Dio is a powerful Http client for Dart, which supports Interceptors, Global configuration, FormData, Request Cancellation, File downloading, Timeout etc.

Concepts we will be covering in this post:

  1. Interceptors Interceptors are a way to intercept and modify http requests before they are sent to the server and to intercept and modify http responses before they are returned to the caller.
  2. Loggers A logger is a way to log the requests and responses to the console.
  3. Retries A retry is a way to retry a failed request.
  4. Token and Refresh Token A token is a way to authenticate a user and a refresh token is a way to refresh that token when it expires.

Setting up dio πŸ”§ #

To use dio, you need to add it to your pubspec.yaml file:

dependencies:
  dio: ^4.0.6
  dio_smart_retry: ^1.3.2 # optional
  pretty_dio_logger: ^1.1.1 # optional

The dio_smart_try and pretty_dio_logger packages are optional, but I’ll be using them in this post, it’s an easy way to log the requests and responses and to retry failed requests, but we will make our own retry interceptor for the refresh token part.
I also make use of flutter_appauth and flutter_secure_storage in the example found in the repo.

Making our custom dio client πŸš€ #

To make a request, you need to create a Dio instance and use the get method to make a GET request:

final dio = Dio();

final response = await dio.get<dynamic>(
  'https://jsonplaceholder.typicode.com/todos/1',
);


print(response.data);

Let’s now create a custom Dio client that we can use in our app:

class DioClient extends DioForNative {
  DioClient({
    List<Interceptor>? interceptors,
    BaseOptions? options,
    int timeOutInMilliseconds = 30 * 1000,
  }) : super(
          options ??
              BaseOptions(
                connectTimeout: timeOutInMilliseconds,
                sendTimeout: timeOutInMilliseconds,
                receiveTimeout: timeOutInMilliseconds,
              ),
        );
}

Now we could simply use a new DioClient instance instence of the Dio in the example above.

Interceptors ⛓️ #

Let’s now add some interceptors to our DioClient:

class DioClient extends DioForNative {
  DioClient(/*...*/) : super(/*...*/) {
    this.interceptors.addAll(
      [
        // Whatever interceptors you want to add from the constructor
        ...?interceptors,
      ],
    );
  }
}

That’s just adding the interceptors passed in the constructor to the interceptors list of the Dio instance. Let’s now have a look at the interceptors we will be using in this post.

Logger interceptor πŸ“œ #

The PrettyDioLogger is a simple interceptor that logs the requests and responses to the console, if you don’t want to use it you can make your own logger interceptor making use of the LogInterceptor class, here I’ll show you how to add both, choose the one you prefer.

// ...
this.interceptors.addAll(
  [
    // ... previously added interceptors

    // Optionally add network Logger interceptor only for debug mode
    if (kDebugMode) ...[
	  // 1. PrettyDioLogger
      PrettyDioLogger(
        requestBody: true,
        responseBody: true,
        // ... other options
        requestHeader: false,
        responseHeader: false,
        error: false,
        request: false,
      ),

	  // 2. LogInterceptor
      LogInterceptor(
        requestBody: true,
        responseBody: true,
        // ... other options
        requestHeader: false,
        responseHeader: false,
        error: false,
        request: false,
        logPrint: (log) {
		  // Customice to your liking
          if (log.toString().isEmpty) return;
          debugPrint('🌐 ${log.toString()}');
        },
      ),
    ],
  ],
);
// ...

How does it look like in the console?

PrettyDioLogger output:

custom dio logger

LogInterceptor output:

custom dio logger

Retry interceptor πŸ” #

The RetryInterceptor is a simple interceptor that retries failed requests, there’s not much to it, you can pass the number of retries you want to make and the Dio instance will retry the request that many times.

// ...
this.interceptors.addAll(
  [
    // ... previously added interceptors

	// RetryInterceptor -- has some more options, check the docs
    RetryInterceptor(dio: this, logPrint: print), 
  ],
);
// ...

Token and Refresh Token interceptor πŸ”‘ #

I don’t really know an out-of-the-box solution, so I’ll be making my own interceptor to handle this.
In the AuthRepository class we will be using the flutter_appauth package to authenticate the user and the flutter_secure_storage package to store the tokens, you can read more about the implementation in the repo, take it as an example the repo does not have a complete implementation, it’s just an example.
The AuthRepository class will be used to authenticate the user and to refresh the token when it expires. To do so, we will add a new InterceptorsWrapper to the DioClient directly inside the AuthRepository class.
This wrapper will have two main methods, the onRequest method will be called before the request is sent to the server, and the onError method will be called before the response is returned to the caller.


class AuthRepository {
  AuthRepository({
    required AuthProvider authProvider,
    required FlutterSecureStorage flutterSecureStorage,
  }) {
    _authProvider = authProvider;
    _secureStorage = flutterSecureStorage;

    final client = authProvider.client;
    client.interceptors.add(
      InterceptorsWrapper(
        onRequest: (request, handler) async {
        },
        onError: (e, handler) async {
        },
      ),
    );
  }

  late final AuthProvider _authProvider;
  late final FlutterSecureStorage _secureStorage;
  // ...

  // Implementation of the signIn, checkSession, refreshToken, signOut, deleteTokens, etc...
  // Please, refer to the repo for more info about the implementation
}

The onRequest method will be used to add the Authorization header to the request, and the onError method will be used to refresh the token when it expires.
Let’s now have a look at the implementation of the onRequest method:

onRequest: (request, handler) async {
  // We add the accessToken to the headers if it's not null
  final accessToken = await _secureStorage.read(key: _accessTokenKey);

  if (accessToken != null) {
    request.headers['Authorization'] = 'Bearer $accessToken';
  }
  debugPrint('[DIO]: Added accessToken [${accessToken != null}]');

  return handler.next(request);
},

Here we basically get the accessToken from the FlutterSecureStorage and add it to the request headers if it’s not null, then we call the handler.next(request) method to continue with the request.
Like this we do not have to add the Authorization header to every request we make, we just have to add the DioClient instance to the AuthProvider class and we are good to go πŸ˜„


Now let’s have a look at the implementation of the onError method:

onError: (e, handler) async {
  // If the statuscode is 401 we try to refresh the token
  if (e.response?.statusCode == 401) {
    // We refresh the token
    await refreshTokens();
    // We add the accessToken to the headers if it's not null
    final accessToken = await _secureStorage.read(
      key: _accessTokenKey,
    );
    if (accessToken != null) {
      debugPrint('[DIO]: Refreshed Tokens');
      e.requestOptions.headers['Authorization'] = 'Bearer $accessToken';

      // Create request with new access token
      final opts = Options(
        method: e.requestOptions.method,
        headers: e.requestOptions.headers,
      );
      final cloneReq = await client.request<void>(
        e.requestOptions.path,
        options: opts,
        data: e.requestOptions.data,
        queryParameters: e.requestOptions.queryParameters,
      );

      return handler.resolve(cloneReq);
    }

    debugPrint("[DIO]: Couldn't refresh Tokens");
  }
},

Here we check if the status code of the response is 401, which, for our case, means that the token has expired, then we call the refreshTokens method to refresh the token, and we add the new accessToken to the request headers, then we create a new request with the new accessToken and we return the response to the caller.

VoilΓ , now we have a working interceptor that not only refreshes the token when it expires, but also adds the accessToken to the request headers, so we don’t have to πŸŽ‰

Conclusion πŸ“ #

In this post, I introduced you to the Dio package, I showed you how to use it to make HTTP requests, and I showed you how to use interceptors to add custom logic to the requests.
Take this as an introduction to the Dio package, there’s a lot more to it, you can check the docs for more info.

I hope you enjoyed it and that you found it useful.
If you have any questions or suggestions, feel free to leave a comment below. πŸ˜„
Thanks for reading! πŸ€“

The full source code with for this post is available here πŸ”

The pubspec.yaml file for this project uses the following dependencies πŸ“¦

dependencies:
  dio: ^4.0.6 # To make HTTP requests and to use the interceptors I created
  dio_smart_retry: ^1.3.2 # Easy way to add retry logic to the requests
  flutter:
    sdk: flutter
  flutter_appauth: ^4.2.1 # Used to authenticate the user and to refresh the token
  flutter_secure_storage: ^6.0.0 # Used to store the tokens in the secure storage
  pretty_dio_logger: ^1.1.1 # For logging the requests

dev_dependencies:
  flutter_test:
    sdk: flutter
  mocktail: ^0.3.0 # For mocking
  very_good_analysis: ^3.1.0 # Used to enforce very good practices πŸ¦„

References πŸ“š #

Author
Carlos GutiΓ©rrez