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?
3 Answers
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.

- 338
- 3
- 10
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.

- 133
- 1
- 7
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;
}
}

- 39
- 1
- 5