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 = {
access_token: string;
refresh_token: string;
};
const LS_KEY = "uhm_auth_tokens_v1";
@@ -12,9 +11,9 @@ function safeParseTokens(raw: string | null): StoredTokens | null {
try {
const v = JSON.parse(raw) as Partial<StoredTokens>;
if (!v || typeof v !== "object") return null;
if (typeof v.access_token !== "string" || typeof v.refresh_token !== "string") return null;
if (!v.access_token.trim() || !v.refresh_token.trim()) return null;
return { access_token: v.access_token, refresh_token: v.refresh_token };
if (typeof v.access_token !== "string") return null;
if (!v.access_token.trim()) return null;
return { access_token: v.access_token };
} catch {
return null;
}
@@ -41,10 +40,6 @@ export function getAccessToken(): string | null {
return getStoredTokens()?.access_token ?? null;
}
export function getRefreshToken(): string | null {
return getStoredTokens()?.refresh_token ?? null;
}
export function clearStoredTokens(): void {
setStoredTokens(null);
}
@@ -72,8 +67,8 @@ export function extractTokensFromResponsePayload(payload: any): StoredTokens | n
tokenContainer?.refreshToken ??
tokenContainer?.refresh ??
null;
if (typeof access === "string" && typeof refresh === "string" && access.trim() && refresh.trim()) {
return { access_token: access, refresh_token: refresh };
if (typeof access === "string" && access.trim()) {
return { access_token: access };
}
return null;
}
+3 -26
View File
@@ -4,7 +4,6 @@ import {
clearStoredTokens,
extractTokensFromResponsePayload,
getAccessToken,
getRefreshToken,
setStoredTokens,
} from "@/auth/tokenStore"
@@ -118,31 +117,11 @@ async function performRefreshAndRetry(originalRequest: any): Promise<AxiosRespon
isRefreshing = true
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 () => {
return refreshApi.post("/auth/refresh", {})
}
let refreshRes: any = null
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
}
}
let refreshRes: any = await tryCookieRefresh()
const nextTokens = extractTokensFromResponsePayload(refreshRes?.data)
if (nextTokens) setStoredTokens(nextTokens)
@@ -159,10 +138,8 @@ async function performRefreshAndRetry(originalRequest: any): Promise<AxiosRespon
} catch (refreshErr: any) {
processQueue(refreshErr)
// 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
// the user was anonymous, and we should just let the error bubble up.
const refreshToken = getRefreshToken()
if (refreshToken && refreshErr?.response?.status === 401) {
// CRITICAL: We only redirect if it's a 401, which means the HttpOnly cookie is missing or invalid.
if (refreshErr?.response?.status === 401) {
clearStoredTokens()
if (typeof window !== "undefined") {
window.location.href = "/signin"
+1 -2
View File
@@ -4,7 +4,6 @@ import { clearStoredTokens, setStoredTokens } from "@/auth/tokenStore";
export type AuthTokens = {
access_token: string;
refresh_token: string;
};
export type CurrentUser = {
@@ -21,7 +20,7 @@ export async function signIn(email: string, password: string): Promise<AuthToken
jsonRequestInit("POST", { email, password }),
{ skipAuth: true }
);
if (res?.access_token && res?.refresh_token) setStoredTokens(res);
if (res?.access_token) setStoredTokens(res);
return res;
}