refactor: remove refresh token handling in favor of HttpOnly cookie-based authentication

This commit is contained in:
taDuc
2026-05-13 17:45:56 +07:00
parent f1d6f22f80
commit c92aaafc33
3 changed files with 9 additions and 38 deletions
+5 -10
View File
@@ -1,6 +1,5 @@
export type StoredTokens = { export type StoredTokens = {
access_token: string; access_token: string;
refresh_token: string;
}; };
const LS_KEY = "uhm_auth_tokens_v1"; const LS_KEY = "uhm_auth_tokens_v1";
@@ -12,9 +11,9 @@ function safeParseTokens(raw: string | null): StoredTokens | null {
try { try {
const v = JSON.parse(raw) as Partial<StoredTokens>; const v = JSON.parse(raw) as Partial<StoredTokens>;
if (!v || typeof v !== "object") return null; if (!v || typeof v !== "object") return null;
if (typeof v.access_token !== "string" || typeof v.refresh_token !== "string") return null; if (typeof v.access_token !== "string") return null;
if (!v.access_token.trim() || !v.refresh_token.trim()) return null; if (!v.access_token.trim()) return null;
return { access_token: v.access_token, refresh_token: v.refresh_token }; return { access_token: v.access_token };
} catch { } catch {
return null; return null;
} }
@@ -41,10 +40,6 @@ export function getAccessToken(): string | null {
return getStoredTokens()?.access_token ?? null; return getStoredTokens()?.access_token ?? null;
} }
export function getRefreshToken(): string | null {
return getStoredTokens()?.refresh_token ?? null;
}
export function clearStoredTokens(): void { export function clearStoredTokens(): void {
setStoredTokens(null); setStoredTokens(null);
} }
@@ -72,8 +67,8 @@ export function extractTokensFromResponsePayload(payload: any): StoredTokens | n
tokenContainer?.refreshToken ?? tokenContainer?.refreshToken ??
tokenContainer?.refresh ?? tokenContainer?.refresh ??
null; null;
if (typeof access === "string" && typeof refresh === "string" && access.trim() && refresh.trim()) { if (typeof access === "string" && access.trim()) {
return { access_token: access, refresh_token: refresh }; return { access_token: access };
} }
return null; return null;
} }
+3 -26
View File
@@ -4,7 +4,6 @@ import {
clearStoredTokens, clearStoredTokens,
extractTokensFromResponsePayload, extractTokensFromResponsePayload,
getAccessToken, getAccessToken,
getRefreshToken,
setStoredTokens, setStoredTokens,
} from "@/auth/tokenStore" } from "@/auth/tokenStore"
@@ -118,31 +117,11 @@ async function performRefreshAndRetry(originalRequest: any): Promise<AxiosRespon
isRefreshing = true isRefreshing = true
try { try {
const refreshToken = getRefreshToken()
const tryHeaderRefresh = async () => {
if (!refreshToken) return null
// Use dedicated refreshApi to handle baseURL and credentials consistently.
return refreshApi.post("/auth/refresh", {}, {
headers: { Authorization: `Bearer ${refreshToken}` }
})
}
const tryCookieRefresh = async () => { const tryCookieRefresh = async () => {
return refreshApi.post("/auth/refresh", {}) return refreshApi.post("/auth/refresh", {})
} }
let refreshRes: any = null let refreshRes: any = await tryCookieRefresh()
try {
refreshRes = (await tryHeaderRefresh()) || (await tryCookieRefresh())
} catch (e: any) {
// If header-based refresh fails (wrong token type), fall back to cookie refresh.
if (refreshToken && e?.response?.status === 401) {
refreshRes = await tryCookieRefresh()
} else {
throw e
}
}
const nextTokens = extractTokensFromResponsePayload(refreshRes?.data) const nextTokens = extractTokensFromResponsePayload(refreshRes?.data)
if (nextTokens) setStoredTokens(nextTokens) if (nextTokens) setStoredTokens(nextTokens)
@@ -159,10 +138,8 @@ async function performRefreshAndRetry(originalRequest: any): Promise<AxiosRespon
} catch (refreshErr: any) { } catch (refreshErr: any) {
processQueue(refreshErr) processQueue(refreshErr)
// Only force logout when refresh token/session is truly invalid (401). // Only force logout when refresh token/session is truly invalid (401).
// CRITICAL: Only redirect if we HAD a refresh token. If we didn't, it means // CRITICAL: We only redirect if it's a 401, which means the HttpOnly cookie is missing or invalid.
// the user was anonymous, and we should just let the error bubble up. if (refreshErr?.response?.status === 401) {
const refreshToken = getRefreshToken()
if (refreshToken && refreshErr?.response?.status === 401) {
clearStoredTokens() clearStoredTokens()
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
window.location.href = "/signin" window.location.href = "/signin"
+1 -2
View File
@@ -4,7 +4,6 @@ import { clearStoredTokens, setStoredTokens } from "@/auth/tokenStore";
export type AuthTokens = { export type AuthTokens = {
access_token: string; access_token: string;
refresh_token: string;
}; };
export type CurrentUser = { export type CurrentUser = {
@@ -21,7 +20,7 @@ export async function signIn(email: string, password: string): Promise<AuthToken
jsonRequestInit("POST", { email, password }), jsonRequestInit("POST", { email, password }),
{ skipAuth: true } { skipAuth: true }
); );
if (res?.access_token && res?.refresh_token) setStoredTokens(res); if (res?.access_token) setStoredTokens(res);
return res; return res;
} }