feat: implement concurrent-safe token refreshing with session-aware error handling

This commit is contained in:
estifanos
2026-08-19 09:00:05 +00:00
parent b860e57227
commit 1eeb4c5cc3
4 changed files with 76 additions and 9 deletions

View File

@@ -2,7 +2,8 @@ import { useEffect, useState, type ReactNode } from 'react';
import { useDispatch } from 'react-redux';
import { PageLoader } from '@ema-platform/ui';
import { authStorage } from '../utils/auth-storage';
import { hydrateAuth, logout, setUser } from '../store/auth.slice';
import { hydrateAuth, logout, setToken, setUser } from '../store/auth.slice';
import { refreshAccessToken } from '../utils/refresh-token';
import type { AuthUser } from '../types/auth.types';
const BASE_API_URL =
@@ -38,10 +39,27 @@ export function AuthBootstrap({ children }: { children: ReactNode }) {
}
try {
const response = await fetch(`${BASE_API_URL}/auth/me`, {
let response = await fetch(`${BASE_API_URL}/auth/me`, {
headers: { Authorization: `Bearer ${token}` },
});
// An expired access token is the normal state after a day away — spend
// the refresh token before deciding the session is over. Without this
// a lapsed token logs the user out on load even though the credential
// to renew it is sitting right next to it in storage.
if (response.status === 401 || response.status === 403) {
try {
const fresh = await refreshAccessToken();
dispatch(setToken(fresh));
response = await fetch(`${BASE_API_URL}/auth/me`, {
headers: { Authorization: `Bearer ${fresh}` },
});
} catch {
// No refresh token, or the server rejected it — fall through to
// the logout below.
}
}
if (response.ok) {
const user = (await response.json()) as AuthUser;
// Keep the persisted session as the source of truth when it is