fix(auth): recover session via refresh cookie when localStorage is empty (#5748)

This commit is contained in:
fiatcode 2026-03-20 18:21:11 +07:00 committed by GitHub
parent be00abe852
commit 551ee1d81f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 21 additions and 10 deletions

View File

@ -61,11 +61,10 @@ export const getAccessToken = (): string | null => {
accessToken = storedToken;
tokenExpiresAt = expiresAt;
}
// Do NOT remove expired tokens here. Callers such as InstanceContext.initialize()
// run concurrently with AuthContext.initialize() via Promise.all. If we eagerly
// delete the expired token from localStorage, hasStoredToken() (called synchronously
// inside AuthContext.initialize()) finds nothing and skips the refresh attempt,
// logging the user out even when the refresh-token cookie is still valid.
// Do NOT remove expired tokens here. getRequestToken() in connect.ts calls
// hasStoredToken() to decide whether to attempt a refresh — if we eagerly delete
// the expired token, it returns null immediately, skipping the refresh and sending
// the request without credentials.
// clearAccessToken() handles proper cleanup after a confirmed auth failure or logout.
}
} catch (e) {

View File

@ -1,7 +1,7 @@
import { useQueryClient } from "@tanstack/react-query";
import { createContext, type ReactNode, useCallback, useContext, useMemo, useState } from "react";
import { clearAccessToken, hasStoredToken } from "@/auth-state";
import { authServiceClient, shortcutServiceClient, userServiceClient } from "@/connect";
import { clearAccessToken, getAccessToken } from "@/auth-state";
import { authServiceClient, refreshAccessToken, shortcutServiceClient, userServiceClient } from "@/connect";
import { userKeys } from "@/hooks/useUserQueries";
import type { Shortcut } from "@/types/proto/api/v1/shortcut_service_pb";
import type { User, UserSetting_GeneralSetting, UserSetting_WebhooksSetting } from "@/types/proto/api/v1/user_service_pb";
@ -53,9 +53,21 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const initialize = useCallback(async () => {
setState((prev) => ({ ...prev, isLoading: true }));
// If there is no stored token at all, the user is not authenticated.
// Skip the network call — there is nothing to refresh and no session to restore.
if (!hasStoredToken()) {
// Try to get or refresh the access token.
// This handles PWA isolated storage scenarios (e.g., iOS Safari) where localStorage
// may be empty but a valid HTTP-only refresh token cookie still exists.
// getAccessToken() returns a cached token or loads from localStorage if valid.
if (!getAccessToken()) {
try {
await refreshAccessToken();
} catch {
// Refresh failed - no valid session
}
}
// If we still don't have a token after refresh attempt, skip getCurrentUser call
// to avoid unnecessary network request for unauthenticated users.
if (!getAccessToken()) {
setState({
currentUser: undefined,
userGeneralSetting: undefined,