Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add requireAuth to <Authenticated /> component #7993

Merged
merged 6 commits into from
Jul 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/Authenticated.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ title: "The Authenticated Component"

# `<Authenticated>`

The `<Authenticated>` 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 `<Route element>` commponent:
The `<Authenticated>` 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 `<Route element>` commponent:

```jsx
import { Admin, CustomRoutes, Authenticated } from 'react-admin';
Expand All @@ -15,7 +15,7 @@ const App = () => (
<Admin authProvider={authProvider}>
<CustomRoutes>
<Route path="/foo" element={<Authenticated><Foo /></Authenticated>} />
<Route path="/bar" element={<Authenticated><Bar /></Authenticated>} />
<Route path="/bar" element={<Authenticated requireAuth><Bar /></Authenticated>} />
<Route path="/anoonymous" element={<Baz />} />
</CustomRoutes>
</Admin>
Expand Down
45 changes: 30 additions & 15 deletions packages/ra-core/src/auth/Authenticated.tsx
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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 = [
* <Route path="/foo" render={() =>
* <Authenticated authParams={{ foo: 'bar' }}>
* <Foo />
* </Authenticated>
* } />
* const customRoutes = [
* <Route
* path="/foo"
* element={
* <Authenticated authParams={{ foo: 'bar' }}>
* <Foo />
* </Authenticated>
* }
* />
* ];
* const App = () => (
* <Admin customRoutes={customRoutes}>
* ...
* <Admin>
* <CustomRoutes>{customRoutes}</CustomRoutes>
* </Admin>
* );
*/
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;
}
11 changes: 8 additions & 3 deletions packages/ra-core/src/auth/useAuthState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -45,17 +47,20 @@ const emptyParams = {};
* return <AnonymousContent />;
* };
*/
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;
};

Expand Down
10 changes: 5 additions & 5 deletions packages/ra-core/src/auth/useAuthenticated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Foo />;
* }
* const CustomRoutes = [
* <Route path="/foo" render={() => <FooPage />} />
* const customRoutes = [
* <Route path="/foo" element={<FooPage />} />
* ];
* const App = () => (
* <Admin customRoutes={customRoutes}>
* ...
* <Admin>
* <CustomRoutes>{customRoutes}</CustomRoutes>
* </Admin>
* );
*/
Expand Down