Reputation: 126
I'm making requests to a server but after some time the access token
expires. I'm using Dio and recently came across interceptors. How do I add an interceptor to all calls and fetch access token
using the refresh token
when a 401
is returned.
I'm storing my tokens
in a Shared Preferences
.Here's my code.
class AuthenticationService {
final Dio _dio;
final LocalDBService _prefs;
AuthenticationService(this._dio, this._prefs);
Future<User?> fetchUser() async {
try {
Tokens _tokens = await _prefs.getTokens();
if (_tokens.accessToken == '') {
return null;
}
final result = await _dio.get(
CURRENT_USER,
options: Options(headers: {
"Authorization": "Bearer ${_tokens.accessToken}",
'Accept': "application/json",
}),
);
User _user = User.fromJson(result.data);
return _user;
} catch (e) {
print("fetcherror");
print(e);
return null;
}
}
}
final authenticationServiceProvider = Provider.autoDispose<AuthenticationService>((ref) {
final _prefs = ref.read(localDBProvider);
final _dio = Dio(); // Need to add interceptors to this
return AuthenticationService(_dio, _prefs);
});
final userProvider = FutureProvider.autoDispose<User?>((ref) {
ref.maintainState = true;
return ref.read(authenticationServiceProvider).fetchUser();
});
How and where do I add the interceptors. Would also want to move the Dio()
instance to its own class
Upvotes: 1
Views: 5403
Reputation: 5601
I would reccomend 2 dio instances, one for logging (authProvider) and another for the rest of your app, so you can lock one and use your authProvider with its own dio instance to refresh the token first
class AuthenticatorInterceptor extends Interceptor {
final AuthenticationService authService;
final Dio dioReference;
AuthenticatorInterceptor(this.authService, this.dioReference);
@override
void onRequest(
RequestOptions options, RequestInterceptorHandler handler) async {
Tokens _tokens = await authService.token;
/// get the token from your authService and do some logic with it:
/// check if its still valid (expirationTime)
/// refresh it if its not, etc.
if (_token.refreshToken != null && _token.hasExpired) { // check somehow if the token is invalid and try to refresh it
try {
dioReference.lock(); // lock the current dio instance
authService.refresh(); //try to get a new access token
_tokens = await authService.token; //get the new token if the refresh was succesful
} on DioError catch (error) { // there was an error refreshing, report it or do some logic
dioReference.clear();
return handler.reject(error);
} finally {
dioReference.unlock(); //unlock the instance and move on
}
}
// if there was no error you can move on with your request with the new token (if any)
options.headers
.putIfAbsent(HttpHeaders.authorizationHeader, () => "Bearer ${_tokens.accessToken}");
return handler.next(options);
}
@override
void onError(DioError error, ErrorInterceptorHandler handler) async {
if (error.type == DioErrorType.response &&
error.response!.statusCode == 401) {
// If you still receive 401 then maybe you should logout and report the user that the refresh token is invalid (maybe the server removed it)
}
return super.onError(error, handler);
}
}
Now create a provider with a Dio instance that you will use with your app (this is a different instance from the one you will use with your AuthService, so you can lock it while asking for the token)
final dioProvider = Provider<Dio>((ref) {
final authenticationService = ref.watch(authenticationServiceProvider);
final Dio dio = Dio();
ref.onDispose(dio.close);
return dio
..interceptors.addAll([
AuthenticatorInterceptor(authenticationService, dio), //this interceptor holds a reference of your authService and the dio instance that uses it
]);
}, name: 'Dio');
And now use it like this
final userProvider = FutureProvider.autoDispose<User?>((ref) {
final dio = ref.watch(dioProvider);
final result = await dio.get(
CURRENT_USER, // I have no idea where you get this
);
User _user = User.fromJson(result.data);
ref.maintainState = true; // the request was succesful, you can mantain the state now
return _user;
});
Of course there is still a lot of logic in the background to do, your AuthService must have at least a token before doing request (there is no purpose to do calls if you know the user is not log) but this should give a general idea of how to proceed with an interceptor
Upvotes: 3