Add JWT auto-refresh: store refresh_token at login, retry on 401
This commit is contained in:
parent
91971379c5
commit
f071e72678
2 changed files with 77 additions and 12 deletions
|
|
@ -120,15 +120,76 @@ export const CONFIG = {
|
|||
}
|
||||
}
|
||||
|
||||
export function apiFetch(url: string, options: RequestInit = {}): Promise<Response> {
|
||||
const token = localStorage.getItem('access_token');
|
||||
const headers = {
|
||||
...options.headers,
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||||
};
|
||||
return fetch(url, {
|
||||
...options,
|
||||
headers,
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
let isRefreshing = false;
|
||||
let refreshSubscribers: ((token: string) => void)[] = [];
|
||||
|
||||
function onTokenRefreshed(token: string) {
|
||||
refreshSubscribers.forEach(cb => cb(token));
|
||||
refreshSubscribers = [];
|
||||
}
|
||||
|
||||
async function refreshAccessToken(): Promise<string | null> {
|
||||
const refreshToken = localStorage.getItem('refresh_token');
|
||||
if (!refreshToken) return null;
|
||||
try {
|
||||
const res = await fetch(`${CONFIG.apiBaseUrl}/api/auth/token/refresh/`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ refresh: refreshToken }),
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json();
|
||||
const newToken = data.access;
|
||||
if (newToken) {
|
||||
localStorage.setItem('access_token', newToken);
|
||||
return newToken;
|
||||
}
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiFetch(url: string, options: RequestInit = {}): Promise<Response> {
|
||||
const token = localStorage.getItem('access_token');
|
||||
const makeRequest = (authToken: string | null) =>
|
||||
fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
...options.headers,
|
||||
...(authToken ? { 'Authorization': `Bearer ${authToken}` } : {}),
|
||||
},
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
const response = await makeRequest(token);
|
||||
|
||||
// Auto-refresh on 401
|
||||
if (response.status === 401) {
|
||||
if (!isRefreshing) {
|
||||
isRefreshing = true;
|
||||
const newToken = await refreshAccessToken();
|
||||
isRefreshing = false;
|
||||
if (newToken) {
|
||||
onTokenRefreshed(newToken);
|
||||
return makeRequest(newToken);
|
||||
} else {
|
||||
// Refresh failed — clear tokens
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
refreshSubscribers = [];
|
||||
}
|
||||
} else {
|
||||
// Queue the request until refresh completes
|
||||
return new Promise(resolve => {
|
||||
refreshSubscribers.push((newToken: string) => {
|
||||
resolve(makeRequest(newToken));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -229,7 +229,8 @@ export const SellerProvider: React.FC<{children: React.ReactNode}> = ({ children
|
|||
})
|
||||
.catch(err => {
|
||||
console.error('Session restore failed:', err);
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('access_token')
|
||||
localStorage.removeItem('refresh_token');
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
|
@ -389,6 +390,7 @@ export const SellerProvider: React.FC<{children: React.ReactNode}> = ({ children
|
|||
.catch(err => console.error('Error logging out:', err))
|
||||
.finally(() => {
|
||||
localStorage.removeItem('access_token')
|
||||
localStorage.removeItem('refresh_token')
|
||||
// Reset all onboarding & profile states to their defaults
|
||||
setIsProfileComplete(false)
|
||||
setProfileStep(1)
|
||||
|
|
@ -491,6 +493,7 @@ export const SellerProvider: React.FC<{children: React.ReactNode}> = ({ children
|
|||
})
|
||||
.then(data => {
|
||||
localStorage.setItem('access_token', data.access_token);
|
||||
if (data.refresh_token) localStorage.setItem('refresh_token', data.refresh_token);
|
||||
navigateTo('profile-completion')
|
||||
})
|
||||
})
|
||||
|
|
@ -518,6 +521,7 @@ export const SellerProvider: React.FC<{children: React.ReactNode}> = ({ children
|
|||
})
|
||||
.then(data => {
|
||||
localStorage.setItem('access_token', data.access_token);
|
||||
if (data.refresh_token) localStorage.setItem('refresh_token', data.refresh_token);
|
||||
if (data.user && data.user.profile) {
|
||||
const profile = data.user.profile;
|
||||
setPhone(profile.phone || '');
|
||||
|
|
|
|||
Loading…
Reference in a new issue