diff --git a/packages/ra-core/src/auth/useAuthState.spec.tsx b/packages/ra-core/src/auth/useAuthState.spec.tsx
index 1f534cc4a83..4470881de6b 100644
--- a/packages/ra-core/src/auth/useAuthState.spec.tsx
+++ b/packages/ra-core/src/auth/useAuthState.spec.tsx
@@ -5,23 +5,21 @@ import { CoreAdminContext } from '../core/CoreAdminContext';
import useAuthState from './useAuthState';
-const UseAuth = ({ children, authParams }: any) => {
- const res = useAuthState(authParams);
- return children(res);
+const UseAuth = (authParams: any) => {
+ const state = useAuthState(authParams);
+ return (
+
+ {state.isLoading && 'LOADING'}
+ {state.authenticated && 'AUTHENTICATED'}
+
+ );
};
-const stateInpector = state => (
-
- {state.isLoading && 'LOADING'}
- {state.authenticated && 'AUTHENTICATED'}
-
-);
-
describe('useAuthState', () => {
it('should return a loading state on mount', () => {
render(
- {stateInpector}
+
);
expect(screen.queryByText('LOADING')).not.toBeNull();
@@ -31,7 +29,7 @@ describe('useAuthState', () => {
it('should return authenticated by default after a tick', async () => {
render(
- {stateInpector}
+
);
await waitFor(() => {
@@ -50,9 +48,7 @@ describe('useAuthState', () => {
};
render(
-
- {stateInpector}
-
+
);
await waitFor(() => {
diff --git a/packages/ra-core/src/auth/useAuthState.ts b/packages/ra-core/src/auth/useAuthState.ts
index 635d6af4be4..3848eae1ccd 100644
--- a/packages/ra-core/src/auth/useAuthState.ts
+++ b/packages/ra-core/src/auth/useAuthState.ts
@@ -1,7 +1,9 @@
-import { useEffect } from 'react';
-
-import { useCheckAuth } from './useCheckAuth';
-import { useSafeSetState } from '../util/hooks';
+import { useMemo } from 'react';
+import { useQuery, UseQueryOptions } from 'react-query';
+import useAuthProvider, { defaultAuthParams } from './useAuthProvider';
+import useLogout from './useLogout';
+import { removeDoubleSlashes, useBasename } from '../routing';
+import { useNotify } from '../notification';
interface State {
isLoading: boolean;
@@ -49,19 +51,62 @@ const emptyParams = {};
*/
const useAuthState = (
params: any = emptyParams,
- logoutOnFailure: boolean = false
+ logoutOnFailure: boolean = false,
+ queryOptions?: UseQueryOptions
): State => {
- const [state, setState] = useSafeSetState({
- isLoading: true,
- authenticated: true, // optimistic
- });
- const checkAuth = useCheckAuth();
- useEffect(() => {
- checkAuth(params, logoutOnFailure)
- .then(() => setState({ isLoading: false, authenticated: true }))
- .catch(() => setState({ isLoading: false, authenticated: false }));
- }, [checkAuth, params, logoutOnFailure, setState]);
- return state;
+ const authProvider = useAuthProvider();
+ const logout = useLogout();
+ const basename = useBasename();
+ const notify = useNotify();
+
+ const result = useQuery(
+ ['auth', 'checkAuth', params],
+ () => {
+ // The authProvider is optional in react-admin
+ return authProvider?.checkAuth(params).then(() => true);
+ },
+ {
+ onError: error => {
+ const loginUrl = removeDoubleSlashes(
+ `${basename}/${defaultAuthParams.loginUrl}`
+ );
+ if (logoutOnFailure) {
+ logout(
+ {},
+ error && error.redirectTo != null
+ ? error.redirectTo
+ : loginUrl
+ );
+ const shouldSkipNotify = error && error.message === false;
+ !shouldSkipNotify &&
+ notify(
+ getErrorMessage(error, 'ra.auth.auth_check_error'),
+ { type: 'warning' }
+ );
+ }
+ },
+ retry: false,
+ ...queryOptions,
+ }
+ );
+
+ return useMemo(() => {
+ return {
+ // If the data is undefined and the query isn't loading anymore, it means the query failed.
+ // In that case, we set authenticated to false unless there's no authProvider.
+ authenticated:
+ result.data ?? result.isLoading ? true : authProvider == null, // Optimisic
+ isLoading: result.isLoading,
+ error: result.error,
+ };
+ }, [authProvider, result]);
};
export default useAuthState;
+
+const getErrorMessage = (error, defaultMessage) =>
+ typeof error === 'string'
+ ? error
+ : typeof error === 'undefined' || !error.message
+ ? defaultMessage
+ : error.message;
diff --git a/packages/ra-core/src/auth/useAuthenticated.spec.tsx b/packages/ra-core/src/auth/useAuthenticated.spec.tsx
index f44b2fb5c61..5c0c7f7272c 100644
--- a/packages/ra-core/src/auth/useAuthenticated.spec.tsx
+++ b/packages/ra-core/src/auth/useAuthenticated.spec.tsx
@@ -5,9 +5,14 @@ import { createMemoryHistory } from 'history';
import { Routes, Route, useLocation } from 'react-router-dom';
import { memoryStore } from '../store';
-import { Authenticated } from './Authenticated';
import { useNotificationContext } from '../notification';
import { CoreAdminContext } from '../core';
+import { useAuthenticated } from '.';
+
+const Authenticated = ({ children, ...params }) => {
+ useAuthenticated({ params });
+ return children;
+};
describe('useAuthenticated', () => {
const Foo = () => Foo
;
@@ -27,6 +32,12 @@ describe('useAuthenticated', () => {
+
+
+
+
+
+
);
expect(authProvider.checkAuth).toBeCalledTimes(1);
@@ -53,7 +64,7 @@ describe('useAuthenticated', () => {
);
const { rerender } = render();
- rerender();
+ rerender();
expect(authProvider.checkAuth).toBeCalledTimes(2);
expect(authProvider.checkAuth.mock.calls[1][0]).toEqual({ foo: 'bar' });
expect(reset).toHaveBeenCalledTimes(0);
@@ -132,19 +143,21 @@ describe('useAuthenticated', () => {
);
await waitFor(() => {
- expect(authProvider.checkAuth.mock.calls[0][0]).toEqual({});
- expect(authProvider.logout.mock.calls[0][0]).toEqual({});
- expect(reset).toHaveBeenCalledTimes(1);
- expect(notificationsSpy).toEqual([
- {
- message: 'ra.auth.auth_check_error',
- type: 'warning',
- notificationOptions: {},
- },
- ]);
- expect(screen.getByLabelText('nextPathname').innerHTML).toEqual(
- '/'
- );
+ expect(authProvider.checkAuth).toHaveBeenCalledTimes(1);
+ });
+ expect(authProvider.checkAuth.mock.calls[0][0]).toEqual({});
+ await waitFor(() => {
+ expect(authProvider.logout).toHaveBeenCalledTimes(1);
});
+ expect(authProvider.logout.mock.calls[0][0]).toEqual({});
+ expect(reset).toHaveBeenCalledTimes(1);
+ expect(notificationsSpy).toEqual([
+ {
+ message: 'ra.auth.auth_check_error',
+ type: 'warning',
+ notificationOptions: {},
+ },
+ ]);
+ expect(screen.getByLabelText('nextPathname').innerHTML).toEqual('/');
});
});
diff --git a/packages/ra-core/src/auth/useAuthenticated.ts b/packages/ra-core/src/auth/useAuthenticated.ts
index 83a3e1d573c..b7554ccdbb2 100644
--- a/packages/ra-core/src/auth/useAuthenticated.ts
+++ b/packages/ra-core/src/auth/useAuthenticated.ts
@@ -1,5 +1,5 @@
-import { useEffect } from 'react';
-import { useCheckAuth } from './useCheckAuth';
+import { UseQueryOptions } from 'react-query';
+import useAuthState from './useAuthState';
/**
* Restrict access to authenticated users.
@@ -26,20 +26,17 @@ import { useCheckAuth } from './useCheckAuth';
*
* );
*/
-export const useAuthenticated = (
- options: UseAuthenticatedOptions = {}
-) => {
- const { enabled = true, params = emptyParams } = options;
- const checkAuth = useCheckAuth();
- useEffect(() => {
- if (enabled) {
- checkAuth(params).catch(() => {});
- }
- }, [checkAuth, enabled, params]);
+export const useAuthenticated = ({
+ params,
+ ...options
+}: UseAuthenticatedOptions = {}) => {
+ useAuthState(params ?? emptyParams, true, options);
};
-export type UseAuthenticatedOptions = {
- enabled?: boolean;
+export type UseAuthenticatedOptions = UseQueryOptions<
+ boolean,
+ any
+> & {
params?: ParamsType;
};