I'm building a Flutter App that primarily utilises API calls to function, however am running into 401 Unauthorised errors after the bearer token expires (every 4 hours).
I have tried to implement a DIO interceptor to handle the request in this scenario, but looking at the network debug logs the request repeatedly sends, even when receiving a Status Code 200 back. (I have replicated a 401 and 200 by using my login page and sending a incorrect password in the requestToken function and hard coding the correct password in the refreshToken function)
I have put all API calls in a separate file apihelper.dart a portion of code looks like the below:
class APIHelper {
StorageService storageService = StorageService();
Dio dio = Dio(
BaseOptions(
connectTimeout: Duration(seconds: 120),
receiveTimeout: Duration(seconds: 120),
),
);
final _storage = const FlutterSecureStorage();
APIHelper() {
dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) async {
final bearerToken = await _storage.read(key: 'token');
final tenantId = await _storage.read(key: 'tenant');
if (options.path != '/api/tokens') {
// Add the access token to the request header
options.headers['Authorization'] = bearerToken;
options.headers['tenant'] = tenantId;
return handler.next(options);
}
options.headers['tenant'] = tenantId;
return handler.next(options);
},
onError: (DioException e, handler) async {
if (e.response?.statusCode == 401) {
// If a 401 response is received, refresh the access token
String newAccessToken = await refreshToken();
// Update the request header with the new access token
e.requestOptions.headers['Authorization'] =
'Bearer $newAccessToken';
// Repeat the request with the updated header
return handler.resolve(await dio.fetch(e.requestOptions));
}
},
),
);
}
My initial RequestToken function is below:
Future requestToken(String username, String password, String tenant) async {
var credsdata = {"email": username, "password": password};
String baseUrl = await getBaseUrl();
dio.options.baseUrl = baseUrl; // Set the dynamic base URL
print('requestToken() Called');
print('requestToken() Body: $credsdata');
try {
Response response = await dio.post('/api/tokens', data: credsdata);
Map<String, dynamic> data = response.data;
var token = data['token'];
var refreshToken = data['refreshToken'];
await storageService.saveStorageData('token', token);
await storageService.saveStorageData('refreshtoken', refreshToken);
print(refreshToken);
return 'Authentication Success';
} on DioException catch (e) {
return e.response?.statusMessage;
}
}
My RefreshToken function looks like:
Future refreshToken() async {
final savedUser = await _storage.read(key: 'username');
final savedPasswd = await _storage.read(key: 'password');
var credsdata = {"email": savedUser, "password": 'TEMP HARDCODED PASSWORD'};
print('refreshToken() Called');
print('refreshToken() Body: $credsdata');
String baseUrl = await getBaseUrl();
dio.options.baseUrl = baseUrl; // Set the dynamic base URL
try {
Response response = await dio.post('/api/tokens', data: credsdata);
Map<String, dynamic> data = response.data;
var token = data['token'];
var refreshToken = data['refreshToken'];
await storageService.saveStorageData('token', token);
await storageService.saveStorageData('refreshtoken', refreshToken);
return token;
} on DioException catch (e) {
return e.response?.statusMessage;
}
}
screenshot of Network Debug The records with a status code 200 have the hardcoded password from refreshToken() and the 401 logs have the password that is being passed using the UI form.
How can I get it to stop trying once a status code 200 is returned?
I have provided how I handled it below. This might help.