diff --git a/__tests__/auth-provider.test.tsx b/__tests__/auth-provider.test.tsx index 5855006f..46a75b82 100644 --- a/__tests__/auth-provider.test.tsx +++ b/__tests__/auth-provider.test.tsx @@ -91,7 +91,7 @@ describe('Auth0Provider', () => { }); it('should handle errors when checking session', async () => { - clientMock.checkSession.mockRejectedValue({ + clientMock.checkSession.mockRejectedValueOnce({ error: '__test_error__', error_description: '__test_error_description__', }); @@ -248,6 +248,7 @@ describe('Auth0Provider', () => { }); it('should provide a logout method', async () => { + clientMock.isAuthenticated.mockResolvedValue(true); const wrapper = createWrapper(); const { waitForNextUpdate, result } = renderHook( () => useContext(Auth0Context), @@ -255,10 +256,33 @@ describe('Auth0Provider', () => { ); await waitForNextUpdate(); expect(result.current.logout).toBeInstanceOf(Function); - await result.current.logout({ returnTo: '__return_to__' }); + act(() => { + result.current.logout(); + }); + expect(clientMock.logout).toHaveBeenCalled(); + // Should not update state until returned from idp + expect(result.current.isAuthenticated).toBe(true); + }); + + it('should update state for local logouts', async () => { + clientMock.isAuthenticated.mockResolvedValue(true); + clientMock.getUser.mockResolvedValue('__test_user__'); + const wrapper = createWrapper(); + const { waitForNextUpdate, result } = renderHook( + () => useContext(Auth0Context), + { wrapper } + ); + await waitForNextUpdate(); + expect(result.current.isAuthenticated).toBe(true); + expect(result.current.user).toBe('__test_user__'); + act(() => { + result.current.logout({ localOnly: true }); + }); expect(clientMock.logout).toHaveBeenCalledWith({ - returnTo: '__return_to__', + localOnly: true, }); + expect(result.current.isAuthenticated).toBe(false); + expect(result.current.user).toBeUndefined(); }); it('should provide a getAccessTokenSilently method', async () => { diff --git a/src/auth0-provider.tsx b/src/auth0-provider.tsx index 25670d5d..d205398a 100644 --- a/src/auth0-provider.tsx +++ b/src/auth0-provider.tsx @@ -4,6 +4,7 @@ import { Auth0ClientOptions, CacheLocation, IdToken, + LogoutOptions, PopupLoginOptions, PopupConfigOptions, RedirectLoginOptions as Auth0RedirectLoginOptions, @@ -224,6 +225,13 @@ const Auth0Provider = (opts: Auth0ProviderOptions): JSX.Element => { dispatch({ type: 'LOGIN_POPUP_COMPLETE', isAuthenticated, user }); }; + const logout = (opts: LogoutOptions = {}): void => { + client.logout(opts); + if (opts.localOnly) { + dispatch({ type: 'LOGOUT' }); + } + }; + return ( { loginWithRedirect: (opts): Promise => client.loginWithRedirect(toAuth0LoginRedirectOptions(opts)), loginWithPopup, - logout: (opts): void => client.logout(opts), + logout, }} > {children} diff --git a/src/reducer.tsx b/src/reducer.tsx index af3da671..514da083 100644 --- a/src/reducer.tsx +++ b/src/reducer.tsx @@ -7,6 +7,7 @@ type Action = isAuthenticated: boolean; user?: User; } + | { type: 'LOGOUT' } | { type: 'ERROR'; error: Error }; /** @@ -28,6 +29,12 @@ export const reducer = (state: AuthState, action: Action): AuthState => { isLoading: false, error: undefined, }; + case 'LOGOUT': + return { + ...state, + isAuthenticated: false, + user: undefined, + }; case 'ERROR': return { ...state,