diff --git a/frontend/src/plugins/api.js b/frontend/src/plugins/api.js index a55b50b..d3e47bf 100644 --- a/frontend/src/plugins/api.js +++ b/frontend/src/plugins/api.js @@ -19,6 +19,34 @@ export function useClient() { api.interceptors.request.use(requestInterceptor); + // Add response interceptor for token refresh + api.interceptors.response.use( + (response) => response, + async (error) => { + const originalRequest = error.config; + + // If error is 401 and we haven't tried to refresh the token yet + if (error.response?.status === 401 && !originalRequest._retry) { + originalRequest._retry = true; + + try { + // Attempt to refresh the token + await authStore.refresh(); + + // Retry the original request with the new token + originalRequest.headers["Authorization"] = `Bearer ${authStore.accessToken}`; + return api(originalRequest); + } catch (refreshError) { + // If refresh fails, logout the user + await authStore.logout(); + return Promise.reject(refreshError); + } + } + + return Promise.reject(error); + } + ); + return api; } diff --git a/frontend/src/stores/authStore.js b/frontend/src/stores/authStore.js index 71dcfd9..dc2160d 100644 --- a/frontend/src/stores/authStore.js +++ b/frontend/src/stores/authStore.js @@ -13,6 +13,17 @@ function getClaimsFromToken(token) { } } +function isTokenExpired(token) { + if (!token) return true; + const claims = getClaimsFromToken(token); + if (!claims) return true; + + // Check if token will expire in the next 5 minutes + const expirationTime = claims.exp * 1000; // Convert to milliseconds + const currentTime = Date.now(); + return currentTime >= expirationTime - 5 * 60 * 1000; // 5 minutes before expiration +} + export const useAuthStore = defineStore( 'auth', () => { @@ -22,7 +33,7 @@ export const useAuthStore = defineStore( const accessToken = useSessionStorage('auth-accessToken', undefined) const refreshToken = useSessionStorage('auth-refreshToken', undefined) - const isAuthenticated = computed(() => !!accessToken.value) + const isAuthenticated = computed(() => !!accessToken.value && !isTokenExpired(accessToken.value)) const userId = computed(() => { const claims = getClaimsFromToken(accessToken.value) @@ -96,20 +107,33 @@ export const useAuthStore = defineStore( } async function refresh() { + if (!refreshToken.value) { + throw new Error('No refresh token available'); + } + try { const response = await clientApi.post( 'api/users/refresh', { - refreshToken: refreshToken + refreshToken: refreshToken.value }); updateTokens({ - accessToken: response.accessToken, - refreshToken: refreshToken - }) + accessToken: response.data.accessToken, + refreshToken: response.data.refreshToken + }); + return true; } catch (error) { - console.error(error) - cleanTokens() + console.error('Token refresh failed:', error); + cleanTokens(); + throw error; + } + } + + // Function to check if token needs refresh + async function ensureValidToken() { + if (isTokenExpired(accessToken.value)) { + await refresh(); } } @@ -121,6 +145,8 @@ export const useAuthStore = defineStore( login, loginWithGoogle, loginWithFacebook, - logout + logout, + refresh, + ensureValidToken } }) \ No newline at end of file