3

I'm using Chopper in my flutter app and what I need to do is, when I get 401 response status code (unauthorized) from my API, I must call another endpoint that will refresh my token and save it into secured storage, when all of this is done, I need to retry the request instantly (so that user cannot notice that his token expired). Is this dooable with Chopper only, or I have to use some other package?

therealsowho
  • 133
  • 1
  • 7

3 Answers3

6

It is possible. You need to use the authenticator field on the Chopper client, e.g.

 final ChopperClient client = ChopperClient(
  baseUrl: backendUrl,
  interceptors: [HeaderInterceptor()],
  services: <ChopperService>[
    _$UserApiService(),
  ],
  converter: converter,
  authenticator: MyAuthenticator(),
);

And your authenticator class, should look something like this:

class MyAuthenticator extends Authenticator {
  @override
  FutureOr<Request?> authenticate(
   Request request, Response<dynamic> response) async {
   if (response.statusCode == 401) {
     String? newToken = await refreshToken();

      final Map<String, String> updatedHeaders =
         Map<String, String>.of(request.headers);

        if (newToken != null) {
          newToken = 'Bearer $newToken';
          updatedHeaders.update('Authorization', (String _) => newToken!,
           ifAbsent: () => newToken!);
         return request.copyWith(headers: updatedHeaders);
        }
      }
    return null;
 }

Admittedly, it wasn't that easy to find/understand (though it is the first property of the chopper client mentioned in their docs), but it is precisely what this property is for. I was going to move to dio myself, but I still had the same issue with type conversion on a retry.

EDIT: You will probably want to keep a retry count somewhere so you don't end up in a loop.

JupiterT
  • 338
  • 3
  • 10
0

I searched couple of days for answer, and I came to conclusion that this is not possible with Chopper... Meanwhile I switched to Dio as my Networking client, but I used Chopper for generation of functions/endpoints.

therealsowho
  • 133
  • 1
  • 7
0

Here is my Authenticator. FYI I'm storing auth-token and refresh-token in preferences.

class AppAuthenticator extends Authenticator {
  @override
    FutureOr<Request?> authenticate(Request request, Response response, [Request? originalRequest]) async {
    if (response.statusCode == HttpStatus.unauthorized) {
      final client = CustomChopperClient.createChopperClient();
      AuthorizationApiService authApi = client.getService<AuthorizationApiService>();
      String refreshTokenValue = await Prefs.refreshToken;
      Map<String, String> refreshToken = {'refresh_token': refreshTokenValue};
      var tokens = await authApi.refresh(refreshToken);
      final theTokens = tokens.body;
      if (theTokens != null) {
        Prefs.setAccessToken(theTokens.auth_token);
        Prefs.setRefreshToken(theTokens.refresh_token);
        request.headers.remove('Authorization');
        request.headers.putIfAbsent('Authorization', () => 'Bearer ${theTokens.auth_token}');
        return request;
      }
    }
    return null;
  }
}

Based on this example: github

And Chopper Client:

class CustomChopperClient {
  static ChopperClient createChopperClient() {
    final client = ChopperClient(
      baseUrl: 'https://example.com/api/',
      services: <ChopperService>[
        AuthorizationApiService.create(),
        ProfileApiService.create(),
        AccountingApiService.create(), // and others
      ],
      interceptors: [
        HttpLoggingInterceptor(),
        (Request request) async => request.copyWith(headers: {
          'Accept': "application/json",
          'Content-type': "application/json",
          'locale': await Prefs.locale,
          'Authorization': "Bearer ${await Prefs.accessToken}",
        }),
      ],
      converter: BuiltValueConverter(errorType: ErrorDetails),
      errorConverter: BuiltValueConverter(errorType: ErrorDetails),
      authenticator: AppAuthenticator(),
    );
    return client;
  }
}