Hello there, Alhumdulillah I have successfully implemented refresh token logic using custom authentication, although there were quite a few challenges on each step and not much information regarding how to implement it correctly, but finally I have a flow which works flawlessly for now.
So you can use interceptors in flutterflow for refreshing the tokens when you are using custom authentication.
LOGIC
Before each API call you need to check if the token has expired, for that you need to access the authenticated user's storage of flutterflow(Authenticated User > (a)Authentication token, (b)refresh token and (c)Authentication token expiry), then you need to check if you have crossed the token's expiry time, if not then you use the same token, if so, then you need to call the refresh token API using the refresh token you have stored.
After you have received the new access token you need to replace your (a) (b) and (c) with the new ones you have received.
IMPLEMENTATION
1-Create a custom function
// Automatic FlutterFlow imports
import '/backend/schema/structs/index.dart';
import '/actions/actions.dart' as action_blocks;
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/custom_code/actions/index.dart'; // Imports other custom actions
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom action code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!
//import these for accessing and updating the authenticated user
import 'package:http/http.dart' as http;
import '/backend/api_requests/api_interceptor.dart';
import 'dart:convert'; // For decoding JWT
import '/auth/custom_auth/custom_auth_user_provider.dart';
import '/auth/custom_auth/auth_util.dart';
class checkAndRefreshToken extends FFApiInterceptor {
@override
Future<ApiCallOptions> onRequest({
required ApiCallOptions options,
}) async {
try {
// accessing the authenticated user's variables (a)(b)and (c) and others
var userData = authManager.userData;//read to provide back
var authUserId = authManager.uid;//read to provide back
String? accessToken = currentAuthenticationToken;
String? userId = currentUserUid;
String? refreshToken = currentAuthRefreshToken;
DateTime? expiryTimeStr = currentAuthTokenExpiration;
if (accessToken == null ||
refreshToken == null ||
expiryTimeStr == null) {
throw Exception('Token not found');
}
// Check if the token has expired
if (DateTime.now().isAfter(expiryTimeStr)) {
// If expired, make a request to refresh the token
final refreshResponse = await http.post(
Uri.parse(
'BASE_URL/refresh-token'), // Update with your actual API URL
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'refresh_token': refreshToken}),//replace with your //request body key
);
//call refresh API
if (refreshResponse.statusCode == 200) {
// Parse the response and update tokens and expiry time according to you //response
final responseBody =
jsonDecode(refreshResponse.body)['success']['data'];
String newAccessToken = responseBody['accessToken'];
String newRefreshToken = responseBody['refreshToken'];
// Decode the new access token to extract the expiry time
final parts = newAccessToken.split('.');
if (parts.length != 3) {
throw Exception('Invalid Token');
}
final payload = jsonDecode(
utf8.decode(base64Url.decode(base64Url.normalize(parts[1]))));
int newExpiryTime = payload['exp'];
// Store the new tokens and expiry time using this function
authManager.updateAuthUserData(
authenticationToken: newAccessToken,
refreshToken: newRefreshToken,
tokenExpiration: decodeTokenExpiry(newAccessToken),
userData: userData,
authUid: authUserId);
// Update the options with the new access token
options.headers['Authorization'] = 'Bearer $newAccessToken';
} else {
throw Exception('Failed to refresh token');
}
} else {
// If the token is still valid, continue with the request
options.headers['Authorization'] = 'Bearer $accessToken';
}
} catch (e) {
throw Exception(e);
}
// Perform any necessary calls or modifications to the [options] before the API call is made.
return options;
}
@override
Future<ApiCallResponse> onResponse({
required ApiCallResponse response,
required Future<ApiCallResponse> Function() retryFn,
}) async {
// Perform any necessary calls or modifications to the [response] prior to returning it.
return response;
}
2- Save this function exactly like this and do not compile because custom code editor doesn't seem to ever identify either the imports or the keywords
3- Go to your API or if multiple APIs then you only have to go the API group > Advanced Group Settings> API interceptors and choose the created function.
and this way you wont have to attach the interceptor to each of your APIs individually.
And you are pretty much set.
Now every time you call any of those APIs, they will be intercepted by this function and refresh logic will get implemented automatically and your app will work flawlessly without any authentication interruption.
The only thing left which holds high priority is that you need to implement this logic on your logged in page/ your splashscreen. See, if you just logged in, you will have a fresh token and while using the app if it gets expired it will be refreshed by the interceptor, but what if you terminate the app, the token expires and you open the app again, see no there is this variable isUserLoggedIn which will be set to false by flutterflow and the interceptor function wont work, because even though it will get the new token for you, it wont be able to update the storage with the new ones because the update function does not allow writing when the isUserLoggedIn variable is false, so in order to tackle that you just need to use the custom authentcation's auth login function and pass the previous authenticated user information to it as this function actually sets the isUserLoggedIn variable to true and you app will be working fine from head to tail.
Well I have tried my best to explain everything regarding this topic, to let others benefit from this achievement, if there are any queries I'll be happy to help also do let me know if there could be any improvements in this.
NOTE: you need to provide everything to the auth login and update authenticated user functions as they will replace everything that you don't provide with null, so if you want to update 1 variable then read the previous ones and provide them to the functions again.
UPDATE: FORGOT ONE IMPORTANT THING
you need to create another custom function decodeTokenExpiry, which you can see is being used in my code, this custom function decodes the provided token, extracts the expiry time and returns it.