The server gives 2 tokens access and refresh during authorization. When refreshing the access token, the server will return a new pair of tokens, and the old refresh token will be revoked, or rather blacklisted.
In the web application I decided that it will refresh the token on a timer, the time of the next refresh gets based on the lifetime of the access token.
It is not quite clear how to synchronize access between several tabs. Local storage is suitable for refresh, but how to deal with access I don't quite understand. If each tab will refresh tokens by itself, there will be a race effect: whoever has time to refresh tokens gets access.
I searched for information about this, but I didn't find anything sensible, I think there should be information, but it's as if everyone needs only one working tab of the application.
You can set up a key in local storage (like isRefreshing) to flag which tab is responsible for refreshing the token. When a tab refreshes, it can set a local storage key to true to indicate this. Other tabs would check the key value before refreshing, and can wait until the key is false to refresh.
Once a refresh is complete and a tab knows it has to store the new tokens (access token and refresh token), the tab can again set values in local storage (such as the accessToken and refreshToken keys). Now, new tabs can find the new values in local storage.
For Flutter web applications, you have a window in dart:html, so you can emit a storage event when local storage has changed, and then have all tabs listen for this event (the storage event) to know when the token is updated and load the new token.
You will want to have one of the tabs act as a "leader" tab (for instance, the first tab opened). The leader tab will be responsible for managing the refresh schedule, while any other tabs will only check for a status. Here are the steps to do this:
Generate a unique id for the tab (for example, Date.now() or a random value) and store it in local storage.The tab with the lowest id will be the leader tab.
class TokenManager {
static const String _myAccessTokenKey = 'accessToken';
static const String _myRefreshTokenKey = 'refreshToken';
static const String _myIsRefreshingKey = 'isRefreshing';
// Let's see if there is a need to refresh the token
static void start(){
html.window.onStorage.listen((event) {
if (event.key == _myAccessTokenKey) {
if(event.key == _myRefreshTokenKey)
loadTokens();
}
});
checkIfNeedsToRefresh();
}
static void checkIfNeedsToRefresh() {
if (html.window.localStorage[_myIsRefreshingKey] == 'true') return; // I'll wait my turn If someone is already at it.
String tabId = html.window.localStorage['myLeaderTabId'] ?? ''; // Grab the leader tab id, or empty if none.
// یکی روز هو گام وفعوا تحاویالمکن دنور اینجا قانونمندی brainفکبد که موضوع یکه اعتبا دی بروی.
if (tabId.isEmpty || int.parse(tabId) > DateTime.now().millisecondsSinceEpoch){
// First I'll create a variable for the present time, since writing
// it directly like is hard.
var inLeaderTimeStamp = DateTime.now(); // save current time
var tempMills = inLeaderTimeStamp.millisecondsSinceEpoch; // I'm going to take time separately
// print('new mills: $tempMills');
String newTabIdValue = tempMills.toString(); // create a string for the storage
html.window.localStorage['myLeaderTabId'] = newTabIdValue; // save the new id
}
if (html.window.localStorage['myLeaderTabId'] == DateTime.now().millisecondsSinceEpoch.toString()){
refreshTokens(); // Let's refresh those tokens, if I'm the leader!
}
}
static Future refreshTokens() async {
html.window.localStorage[_myIsRefreshingKey] = 'true';
try {
html.window.localStorage[_myAccessTokenKey] = 'new_access_token';
html.window.localStorage[_myRefreshTokenKey] = 'new_refresh_token';
}
finally {
html.window.localStorage[_myIsRefreshingKey] = 'false';
}
}
static void loadTokens() {
String accessToken = html.window.localStorage[_myAccessTokenKey] ?? '';
String refreshToken = html.window.localStorage[_myRefreshTokenKey];
}
}