Streamlining JWT Token Refresh in Flutter with Proactive Handling
Tags: flutter, graphql, mobile, tutorial
Published: 2023-11-06 15:03:18

## Introduction

In the realm of app development, ensuring a secure and seamless user authentication flow is paramount. Utilizing JSON Web Tokens (JWT) for user sessions is a common practice that embodies security and efficiency.

JWT tokens usually come as a pair: access token and refresh token.

1. Access token grants the user authentication and is short-lived (can range from 15 minutes to 2 hours)
    
2. Refresh token is used to refresh the access token after it's expired and is usually long-lived (can vary from 2 weeks to 2 months or longer).
    

There are generally 2 ways to ensure that the refreshing is done seamlessly: proactive and reactive.

1. Proactive approach checks if the access token expired and tries to refresh it before executing the request.
    
2. Reactive approach executes the request first. Then, if the request fails with a status code [401 Unauthorised](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401), the access token is refreshed and the request is executed again.
    

In this article, I'll show you how to use the proactive approach to JWT token refresh in Flutter and illustrate how to integrate it with the most popular libraries like [`dio`](https://pub.dev/packages/dio) and [`graphql_flutter`](https://pub.dev/packages/graphql_flutter).

## Writing a custom `http.BaseClient`

Choosing to extend `http.BaseClient` is necessary to check the access token validity and to refresh it before actually executing the HTTP request. It's also very convenient because you can use it for both REST and GraphQL clients since they both rely on HTTP protocol.

Below is a code snippet showing a custom HTTP client class in Flutter, designed to proactively handle JWT token refreshing:

```dart
import 'package:http/http.dart' as http;
import 'package:jwt_decoder/jwt_decoder.dart';

class AuthHttpClient extends http.BaseClient {
  AuthHttpClient({
    String? initialAccessToken,
    required this.refreshAccessToken,
    required this.onRefreshAccessTokenFailed,
  }) : _accessToken = initialAccessToken ?? '';

  String _accessToken;
  final Future<String?> Function() refreshAccessToken;
  final void Function() onRefreshAccessTokenFailed;

  final http.Client _client = http.Client();

  @override
  Future<http.StreamedResponse> send(http.BaseRequest request) async {
    await _ensureValidAccessToken();
    request.headers['Authorization'] = 'Bearer $_accessToken';
    return _client.send(request);
  }

  @override
  void close() {
    _client.close();
    super.close();
  }
  
  Future<String> fetchValidAccessToken() async {
    await _ensureValidAccessToken();
    return _accessToken;
  }

  Future<void> _ensureValidAccessToken() async {
    if (_accessToken.isEmpty || JwtDecoder.isExpired(_accessToken)) {
      final result = await refreshAccessToken();
      if (result == null) {
        onRefreshAccessTokenFailed();
      } else {
        _accessToken = result;
      }
    }
  }
}
```

`AuthHttpClient` is a class which takes in 3 parameters:

* `initialAccessToken` *(optional)*: Initial access token. If it's not provided (null), the \_accessToken is initialized to an empty string. This allows the AuthHttpClient to be instantiated without an initial token, and it will attempt to refresh the token when it's first needed.
    
* `refreshAccessToken`: A function to refresh the access token when needed.
    
* `onRefreshAccessTokenFailed`: A callback function that is invoked if the `refreshAccessToken` function fails to retrieve a new token.
    

The `send` method is overridden to ensure that a valid access token is available before attaching it to the Authorization header of outgoing HTTP requests.

The `fetchValidAccessToken` provides a way to explicitly fetch a valid token. It can be used when you need a valid token, but you are unable to use `AuthHttpClient` directly.

The `_ensureValidAccessToken` is responsible for validating the current access token. If the token is empty or expired([`jwt_decoder`](https://pub.dev/packages/jwt_decoder) package's `isExpired` method is used here) the `refreshAccessToken` function is invoked to attempt a token refresh. In case the token refresh fails (this is indicated by a `null` return value), the `onRefreshFailed` callback is triggered to handle the failure scenario. Otherwise, it updates the private `_accessToken` with the new valid token.

The whole process can be visualized with a flow chart:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ubkw89jkd21kbszb9s0y.png)

The most common way to handle refreshing access token failure is to log the user out (logging the user obtains the new tokens). The easiest way I've found to do this is to expose a `Stream` or use a premade solution like [`eventbus`](https://pub.dev/packages/event_bus).

## Dio Integration

Dio is a powerful library for making HTTP requests. To use `AuthHttpClient` with Dio, you can create a custom Dio interceptor that utilizes `AuthHttpClient` to handle the authorization header and token refreshing.

```dart
class AuthInterceptor extends Interceptor {
  final AuthHttpClient authHttpClient;

  AuthInterceptor(this.authHttpClient);

  @override
  onRequest(RequestOptions options) async {
    final token = await authHttpClient.fetchValidAccessToken();
    options.headers['Authorization'] = 'Bearer $token';
    return options;
  }
}
```

## GraphQL Integration

For handling GraphQL queries, you can choose a library such as [`ferry`](https://pub.dev/packages/ferry) or [`graphql_flutter`](https://pub.dev/packages/graphql_flutter). Create a custom HTTP link that employs `AuthHttpClient` to manage the authorization header and token refreshing, ensuring secure GraphQL queries.

```dart
final exampleAccessToken = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2huRG9lIiwiaWF0IjoxNjMwMjI5MDIyLCJleHAiOjE2MzAyMzI2MjJ9.W27ZMQHnWt_6PxgVPz_1kMb6P_0VqGjI4Hq9Qd5zt8M'

final authHttpClient = AuthHttpClient(
    initialAccessToken: exampleAccessToken,
    refreshAccessToken: () async {
      // Implement your token refresh logic here.
      return 'your_new_access_token';
    },
    onRefreshAccessTokenFailed: () {
      print('Token refresh failed');
    },
  );

  final httpLink = HttpLink(
    'https://your-graphql-endpoint.com/graphql',
    httpClient: authHttpClient,
  );

  final client = GraphQLClient(
    cache: GraphQLCache(),
    link: httpLink,
  );
```

## Conclusion

Mastering the proactive JWT token refreshing approach in Flutter and integrating it with HTTP client libraries like Dio or Ferry makes the app's authentication flow more robust and testable.

This article provides the insights and code snippets to help you achieve a secure and user-friendly app. By handling token refreshes proactively, you ensure a smooth user experience, making sure your app remains secure and user-centric, even as the JWTs go through their natural lifecycle.

If you have enjoyed or found this helpful, follow [my blog](https://dinkomarinac.me/) for more content like this!