diff --git a/src/__tests__/auth-context.test.tsx b/src/__tests__/auth-context.test.tsx index a8acbdef..c4d8bb28 100644 --- a/src/__tests__/auth-context.test.tsx +++ b/src/__tests__/auth-context.test.tsx @@ -92,7 +92,7 @@ describe('AuthContext', () => { events, } as any; const onSignIn = vi.fn(); - + await act(async () => { render( @@ -423,4 +423,37 @@ describe('AuthContext', () => { expect(u.events.removeSilentRenewError).toHaveBeenCalledTimes(1); expect(callbacks).toHaveLength(0); }); -}); \ No newline at end of file + + it('should silently renew expired access token when user has a refresh token and auto login is disabled', async () => { + // given: a stored user whose access token has expired and + // user has a refresh token and a successful silent renewal + const u = { + getUser: async () => ({ + access_token: 'old-token', + expired: true, + refresh_token: 'refresh-token', + }), + signinSilent: vi.fn(), + signinRedirect: vi.fn(), + events, + } as any; + + let result: any; + await act(async () => { + result = render( + + + {value => + value?.userData && ( + Received: {value.userData.access_token} + ) + } + + , + ); + }); + + // then: it renews silently, exposes the renewed user, and does NOT redirect + expect(u.signinSilent).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/auth-context.tsx b/src/auth-context.tsx index 6fb70e70..10513c11 100644 --- a/src/auth-context.tsx +++ b/src/auth-context.tsx @@ -117,13 +117,16 @@ export const AuthProvider: FC> = ({ await onSignOut(); } }, [onSignOut]); - const signInCallbackHooks = useCallback(async (url?: string): Promise => { - const userFromCallback = await userManager.signinCallback(url) ?? null; - setUserData(userFromCallback); - if (onSignIn) { - await onSignIn(userFromCallback); - } - }, [userManager, onSignIn]); + const signInCallbackHooks = useCallback( + async (url?: string): Promise => { + const userFromCallback = (await userManager.signinCallback(url)) ?? null; + setUserData(userFromCallback); + if (onSignIn) { + await onSignIn(userFromCallback); + } + }, + [userManager, onSignIn], + ); const signInPopupHooks = useCallback(async (): Promise => { const userFromPopup = await userManager.signinPopup(); setUserData(userFromPopup); @@ -172,6 +175,11 @@ export const AuthProvider: FC> = ({ const state = onBeforeSignIn ? onBeforeSignIn() : undefined; await userManager.signinRedirect({ ...autoSignInArgs, state }); } + // If there is an existing user whose access token has expired + // but has a refresh token, attempt a silent renewal + else if (user && user.refresh_token) { + await userManager.signinSilent(); + } } // Otherwise if the user is already signed in, set the user data. else if (isMountedRef.current) { @@ -242,4 +250,4 @@ export const AuthProvider: FC> = ({ }, [userManager, isLoading, userData, signInPopupHooks, signOutHooks]); return {children}; -}; \ No newline at end of file +};