diff --git a/docs/Authenticated.md b/docs/Authenticated.md index 535f4a2c2e7..f31e01ba4e4 100644 --- a/docs/Authenticated.md +++ b/docs/Authenticated.md @@ -5,7 +5,7 @@ title: "The Authenticated Component" # `` -The `` component calls [the `useAuthenticated()` hook](./useAuthenticated.md), and renders its child component - unless the authentication check fails. Use it as an alternative to the `useAuthenticated()` hook when you can't use a hook, e.g. inside a `` commponent: +The `` component calls [the `useAuthState()` hook](./useAuthState.md), and by default optimistically renders its child component - unless the authentication check fails. Use it as an alternative to the `useAuthenticated()` hook when you can't use a hook, or you want to support pessimistic mode by setting `requireAuth` prop, e.g. inside a `` commponent: ```jsx import { Admin, CustomRoutes, Authenticated } from 'react-admin'; @@ -15,7 +15,7 @@ const App = () => ( } /> - } /> + } /> } /> diff --git a/packages/ra-core/src/auth/Authenticated.tsx b/packages/ra-core/src/auth/Authenticated.tsx index 95d8ccbc405..11fe00565ac 100644 --- a/packages/ra-core/src/auth/Authenticated.tsx +++ b/packages/ra-core/src/auth/Authenticated.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { ReactNode } from 'react'; -import { useAuthenticated } from './useAuthenticated'; +import useAuthState from './useAuthState'; /** * Restrict access to children to authenticated users. @@ -10,36 +10,51 @@ import { useAuthenticated } from './useAuthenticated'; * Use it to decorate your custom page components to require * authentication. * + * By default this component is optimistic: it does not block + * rendering children when checking authentication, but this mode + * can be turned off by setting `requireAuth` to true. + * * You can set additional `authParams` at will if your authProvider * requires it. * - * @see useAuthenticated + * @see useAuthState * * @example - * import { Authenticated } from 'react-admin'; + * import { Admin, CustomRoutes, Authenticated } from 'react-admin'; * - * const CustomRoutes = [ - * - * - * - * - * } /> + * const customRoutes = [ + * + * + * + * } + * /> * ]; * const App = () => ( - * - * ... + * + * {customRoutes} * * ); */ export const Authenticated = (props: AuthenticatedProps) => { - const { authParams, children } = props; - useAuthenticated({ params: authParams }); - // render the child even though the useAuthenticated() call isn't finished (optimistic rendering) - // the above hook will log out if the authProvider doesn't validate that the user is authenticated + const { authParams, children, requireAuth = false } = props; + + // this hook will log out if the authProvider doesn't validate that the user is authenticated + const { isLoading, authenticated } = useAuthState(authParams, true); + + // in pessimistic mode don't render the children until authenticated + if ((requireAuth && isLoading) || !authenticated) { + return null; + } + + // render the children in optimistic rendering or after authenticated return <>{children}; }; export interface AuthenticatedProps { children: ReactNode; authParams?: object; + requireAuth?: boolean; } diff --git a/packages/ra-core/src/auth/useAuthState.ts b/packages/ra-core/src/auth/useAuthState.ts index 1ef3263f8c4..635d6af4be4 100644 --- a/packages/ra-core/src/auth/useAuthState.ts +++ b/packages/ra-core/src/auth/useAuthState.ts @@ -29,6 +29,8 @@ const emptyParams = {}; * * @param {Object} params Any params you want to pass to the authProvider * + * @param {Boolean} logoutOnFailure: Optional. Whether the user should be logged out if the authProvider fails to authenticate them. False by default. + * * @returns The current auth check state. Destructure as { authenticated, error, isLoading }. * * @example @@ -45,17 +47,20 @@ const emptyParams = {}; * return ; * }; */ -const useAuthState = (params: any = emptyParams): State => { +const useAuthState = ( + params: any = emptyParams, + logoutOnFailure: boolean = false +): State => { const [state, setState] = useSafeSetState({ isLoading: true, authenticated: true, // optimistic }); const checkAuth = useCheckAuth(); useEffect(() => { - checkAuth(params, false) + checkAuth(params, logoutOnFailure) .then(() => setState({ isLoading: false, authenticated: true })) .catch(() => setState({ isLoading: false, authenticated: false })); - }, [checkAuth, params, setState]); + }, [checkAuth, params, logoutOnFailure, setState]); return state; }; diff --git a/packages/ra-core/src/auth/useAuthenticated.ts b/packages/ra-core/src/auth/useAuthenticated.ts index 75fa3cfe605..83a3e1d573c 100644 --- a/packages/ra-core/src/auth/useAuthenticated.ts +++ b/packages/ra-core/src/auth/useAuthenticated.ts @@ -12,17 +12,17 @@ import { useCheckAuth } from './useCheckAuth'; * requires it. * * @example - * import { useAuthenticated } from 'react-admin'; + * import { Admin, CustomRoutes, useAuthenticated } from 'react-admin'; * const FooPage = () => { * useAuthenticated(); * return ; * } - * const CustomRoutes = [ - * } /> + * const customRoutes = [ + * } /> * ]; * const App = () => ( - * - * ... + * + * {customRoutes} * * ); */