diff --git a/packages/react/src/hooks/__tests__/useAuth.test.tsx b/packages/react/src/hooks/__tests__/useAuth.test.tsx index b395627fc15..40255c87ee0 100644 --- a/packages/react/src/hooks/__tests__/useAuth.test.tsx +++ b/packages/react/src/hooks/__tests__/useAuth.test.tsx @@ -312,6 +312,49 @@ describe('useDerivedAuth', () => { expect(errorThrower.throw).toHaveBeenCalledWith(invalidStateError); }); + it('returns loading state when sessionId and userId are present but sessionClaims is missing', () => { + const authObject = { + sessionId: 'session123', + userId: 'user123', + signOut: vi.fn(), + getToken: vi.fn(), + }; + + const { + result: { current }, + } = renderHook(() => useDerivedAuth(authObject)); + + expect(current.isLoaded).toBe(false); + expect(current.isSignedIn).toBeUndefined(); + expect(current.sessionId).toBeUndefined(); + expect(current.userId).toBeUndefined(); + expect(current.sessionClaims).toBeUndefined(); + }); + + it('returns signed in without org when orgId is present but orgRole is missing', () => { + const authObject = { + sessionId: 'session123', + sessionClaims: stubSessionClaims({ sessionId: 'session123', userId: 'user123', orgId: 'org123' }), + userId: 'user123', + orgId: 'org123', + orgRole: undefined, + signOut: vi.fn(), + getToken: vi.fn(), + }; + + const { + result: { current }, + } = renderHook(() => useDerivedAuth(authObject)); + + expect(current.isLoaded).toBe(true); + expect(current.isSignedIn).toBe(true); + expect(current.sessionId).toBe('session123'); + expect(current.userId).toBe('user123'); + expect(current.orgId).toBeNull(); + expect(current.orgRole).toBeNull(); + expect(current.orgSlug).toBeNull(); + }); + it('uses provided has function if available', () => { const mockHas = vi.fn().mockReturnValue(false); const authObject = { diff --git a/packages/shared/src/authorization.ts b/packages/shared/src/authorization.ts index db2af474f93..f6d9cf76b40 100644 --- a/packages/shared/src/authorization.ts +++ b/packages/shared/src/authorization.ts @@ -342,6 +342,25 @@ const resolveAuthState = ({ } as const; } + // Session exists but claims aren't available yet (e.g. during client hydration + // before a token has been fetched). Treat as loading state. + if (!!sessionId && !!userId && !sessionClaims) { + return { + actor: undefined, + getToken, + has: () => false, + isLoaded: false, + isSignedIn: undefined, + orgId: undefined, + orgRole: undefined, + orgSlug: undefined, + sessionClaims: undefined, + sessionId: undefined, + signOut, + userId: undefined, + } as const; + } + if (!!sessionId && !!sessionClaims && !!userId && !!orgId && !!orgRole) { return { actor: actor || null, @@ -359,7 +378,7 @@ const resolveAuthState = ({ } as const; } - if (!!sessionId && !!sessionClaims && !!userId && !orgId) { + if (!!sessionId && !!sessionClaims && !!userId) { return { actor: actor || null, getToken,