refactor: remove refresh token handling in favor of HttpOnly cookie-based authentication
This commit is contained in:
+5
-10
@@ -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
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user