I have a problem with my BloC provider in one of my Flutter applications. I use Chopper as HTTP client and it creates a Chopper instance right at the beginning of the app which I then inject with get_it wherever I want to fetch data from my API.
In the meantime I have implemented a ChopperAuthenticator which fetches a new token from the API when the old one expires (statuscode 401). But the problem is, if the refresh-token is expired too, the user should be logged out. For this I just check if the fetching of the refresh-token was successful, if this is not the case my authentication BloC should be notified that the user is not logged in anymore.
But I can't call BlocProvider.of<AuthenticationBloc>(context).add(Logout()); because in the chopper instance the build context is not known. Is there another way to change the state of a BloC? Do you have any ideas how I can rebuild the whole thing to make it work?
FlutterSecureStorage storage = locator.getIt<FlutterSecureStorage>();
UserRepository userRepository = locator.getIt<UserRepository>();
Swagger getClient(String baseUrl) {
return Swagger.create(
baseUrl: baseUrl,
authenticator: ChopperAuthenticator(),
interceptors: [
MobileDataInterceptor(),
const HeadersInterceptor(
{
'content-type': 'application/json',
'Accept': 'application/json',
},
),
HttpLoggingInterceptor(),
(Response response) async {
return response;
},
(Request request) async {
final String? token = await storage.read(key: 'accessToken');
if (token != null && !request.url.contains('refresh')) {
final Map<String, String> map = {'authorization': 'Bearer $token'};
request.headers.addAll(map);
}
return request;
},
],
);
}
class ChopperAuthenticator extends Authenticator {
@override
FutureOr<Request?> authenticate(
Request request,
Response<dynamic> response, [
Request? originalRequest,
]) async {
if (response.statusCode == 401) {
final String? refreshToken = await storage.read(
key: 'refreshToken',
);
final String? accessToken = await storage.read(
key: 'accessToken',
);
final Response newResponse =
await userRepository.apiClient.apiAuthenticateRefreshTokenPost(
body: RefreshRequestDTO(
accessToken: accessToken,
refreshToken: refreshToken,
),
);
if (newResponse.isSuccessful) {
await storage.write(
key: 'accessToken',
value: newResponse.body['accessToken'].toString(),
);
await storage.write(
key: 'refreshToken',
value: newResponse.body['refreshToken'].toString(),
);
String? newToken = newResponse.body['accessToken'].toString();
final Map<String, String> updatedHeaders =
Map<String, String>.of(request.headers);
newToken = 'Bearer $newToken';
updatedHeaders.update(
'Authorization',
(String _) => newToken!,
ifAbsent: () => newToken!,
);
debugPrint('Token renewed');
return request.copyWith(headers: updatedHeaders);
} else {
<Here should be the logic to log out the user>
}
}
return null;
}
}
Quick and dirty solution:
Since you are using
getItyou should add it to it when creating the bloc like:Then in the log-out section you could just call
Proper solution:
Create a repository that holds authentication. This repository should have a stream that your
AuthBlocis subscribed to. Inject the repository into your chopperAuthenticatorand from there manipulate the stream of the repository.Your
AuthBlocshould react to stream changes and therefore logout the user.This approach would comply with the official docs https://bloclibrary.dev/#/architecture