Flutter : Implement Retry Request for Refresh Token with Interceptors(http).

Lavkant Kachhwaha
6 min readDec 1, 2022

Tokens are pieces of data that carry just enough information to facilitate the process of determining a user’s identity or authorizing a user to perform an action. All in all, tokens are artifacts that allow application systems to perform the authorization and authentication process.

What’s an access token?

When a user log in, the authorization server issues an access token, which is an artifact that client applications can use to make secure calls to an API server. When a client application needs to access protected resources on a server on behalf of a user, the access token lets the client signal to the server that it has received authorization by the user to perform certain tasks or access certain resources.

Decoded Access Token(JWT Format) :

{
"iss": "https://YOUR_DOMAIN/",
"sub": "auth0|123456",
"aud": [
"my-api-identifier",
"https://YOUR_DOMAIN/userinfo"
],
"azp": "YOUR_CLIENT_ID",
"exp": 1489179954,
"iat": 1489143954,
"scope": "openid profile email address phone read:appointments"
}

What Is a Refresh Token?

For security purposes, access tokens may be valid for a short amount of time. Once they expire, client applications can use a refresh token to “refresh” the access token. That is, a refresh token is a credential artifact that lets a client application get new access tokens without having to ask the user to log in again.

The client application can get a new access token as long as the refresh token is valid and unexpired. Consequently, a refresh token that has a very long lifespan could theoretically give infinite power to the token bearer to get a new access token to access protected resources anytime. The bearer of the refresh token could be a legitimate user or a malicious user. As such, security companies, such as Auth0, create mechanisms to ensure that this powerful token is mainly held and used continuously by the intended parties.

  +--------+                                           +---------------+
| |--(A)------- Authorization Grant --------->| |
| | | |
| |<-(B)----------- Access Token -------------| |
| | & Refresh Token | |
| | | |
| | +----------+ | |
| |--(C)---- Access Token ---->| | | |
| | | | | |
| |<-(D)- Protected Resource --| Resource | | Authorization |
| Client | | Server | | Server |
| |--(E)---- Access Token ---->| | | |
| | | | | |
| |<-(F)- Invalid Token Error -| | | |
| | +----------+ | |
| | | |
| |--(G)----------- Refresh Token ----------->| |
| | | |
| |<-(H)----------- Access Token -------------| |
+--------+ & Optional Refresh Token +---------------+

Flutter Implementation :

What is an Interceptor?

  • Interceptors as the name suggest, intercept something. It basically allows us to intercept incoming or outgoing HTTP requests using the HttpClient.
  • Interceptors are a way to do some work for every single HTTP request or response.

With Interceptor We can :

  • Add a token or some custom HTTP header,
  • Catch HTTP responses to do some custom formatting (i.e. convert CSV to JSON) before handing the data over to your service/component.
  • Log all HTTP activity in the console, etc
  • In our situation, instead of providing the token to each individual request, we only need to add it once when making the request to the server.

Plugins Used :

Http Interceptors(Contracts)

class LoggingInterceptor implements InterceptorContract {
@override
Future<RequestData> interceptRequest({required RequestData data}) async {
print(data.toString());
//ADD TOKEN TO REQUEST
//ADD REQUEST LOGS HERE
data.headers.clear();
data.headers['authorization'] = 'Bearer ' + token!;
data.headers['content-type'] = 'application/json';
return data;
}

@override
Future<ResponseData> interceptResponse({required ResponseData data}) async {
//ADD RESPONSE LOGS HERE
print(data.toString());
return data;
}

}

Retry Policy/Retrying requests

Sometimes you need to retry a request due to different circumstances, an expired token is a really good example. Here’s how you could potentially implement an expired token retry policy with http_interceptor.

class ExpiredTokenRetryPolicy extends RetryPolicy {
//Number of retry
@override
int maxRetryAttempts = 2;

@override
Future<bool> shouldAttemptRetryOnException(
Exception reason,
BaseRequest request)
async
{
log(reason.toString());
return false;
}

@override
Future<bool> shouldAttemptRetryOnResponse(ResponseData response) async
{
if ( response.statusCode == 401) {
// CALL REFRESH TOKEN API

// UPDATE TOKEN
return true;
}

return false;
}
}

You can also set the maximum amount of retry attempts with maxRetryAttempts property or override the shouldAttemptRetryOnException if you want to retry the request after it failed with an exception.

API Service

Create http client with InterceptedClient builder function and pass all necessary interceptors(eg. logging, auth).
Add Retry Policy with ExpiredTokenRetryPolicy that you have created earlier.

static final http = InterceptedClient.build(
// INTERCEPTORS,
interceptors: [AuthorizationInterceptor()],
// RETRY POLICY
retryPolicy: ExpiredTokenRetryPolicy(),
);
import 'package:http_interceptor/http/intercepted_client.dart';

class APIService {
static final http = InterceptedClient.build(
// INTERCEPTORS,
interceptors: [AuthorizationInterceptor()],
// RETRY POLICY
retryPolicy: ExpiredTokenRetryPolicy(),
);

static Future<dynamic> httpGetRequest(String endPoint) async {
var token = "";
http.Response response;
try {
//Request with interceptor client
response = await http.get(Uri.parse('$endPoint'), headers: {
'Content-type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer $token',
});
} on SocketException {
throw FetchDataException("No Internet");
} on FormatException {
throw FetchDataException("Bad Response Format");
}
return responseHandler(response);
}


dynamic responseHandler(http.Response response) {
String responseJson = response.body;
final jsonResponse = jsonDecode(responseJson);
switch (response.statusCode) {
case 200:
return jsonResponse;
case 400:
throw BadRequestException(jsonResponse['message']);
case 401:
throw InvalidInputException(jsonResponse['message']);
default:
throw Exception(jsonResponse['message']);
}
}

Retry Request :

When Retry Policy triggers, we are only intrested in “Status Code : 401”, will call refreshToken API and overrides the access token with received access token from refreshToken API. In case of RefreshToken is expired (or maxRetryAttempts reached) then responseHandler() function will be triggered and we can logout the User.

class ExpiredTokenRetryPolicy extends RetryPolicy {
//Number of retry
@override
int maxRetryAttempts = 2;

@override
Future<bool> shouldAttemptRetryOnResponse(ResponseData response) async {
//This is where we need to update our token on 401 response
if (response.statusCode == 401) {
//Refresh your token here. Make refresh token method where you get new token from
//API and set it to your local data
await refreshToken(); //Find bellow the code of this function
return true;
}
return false;
}
}


Future<void> refreshToken() async {
log("Refresh Token Initiated");
try {
final response =
await RefreshTokenService.refreshToken();
log(response.token);
//REPLACE AUTH TOKEN WITH RECEIVED TOKEN

} catch (e) {
log('Failed at refreshToken $e');

}
log("Refresh Token Terminated");
}

Final Results : Implemented Refresh Token in your Flutter App with interceptors.

Additional WIP:

Dio Interceptors

class DioInterceptor extends Interceptor {
final _prefsLocator = getIt.get<SharedPreferenceHelper>();

@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
options.headers['Authorization'] = _prefsLocator.getUserToken();
//ADD REQUEST LOGS HERE
super.onRequest(options, handler);
}

@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
// TODO: implement onResponse
//ADD RESPINSE LOGS HERE
super.onResponse(response, handler);
}

@override
void onError(DioError err, ErrorInterceptorHandler handler) {
// TODO: implement onError
super.onError(err, handler);
}
}

Dio Retry Policy : WIP

Lavkant Kachhwaha
Lavkant Kachhwaha

Written by Lavkant Kachhwaha

Flutter Enthusiast & Engineering @ CoinDCX

Responses (1)

Write a response