Skip to content

Commit bb602ad

Browse files
committed
Extract Security guide
1 parent 82e0130 commit bb602ad

File tree

4 files changed

+295
-10
lines changed

4 files changed

+295
-10
lines changed

docs/Authentication.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
---
22
layout: default
3-
title: "Security"
3+
title: "Auth Provider Setup"
44
---
55

6-
# Security
6+
# Auth Provider Setup
77

88
<video controls autoplay playsinline muted loop>
9-
<source src="./img/login.webm" type="video/webm"/>
109
<source src="./img/login.mp4" type="video/mp4"/>
1110
Your browser does not support the video tag.
1211
</video>

docs/DataFetchingGuide.md

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ The Data Provider is a key part of react-admin's architecture. By standardizing
5050

5151
## Calling The Data Provider
5252

53-
React-admin uses [TanStack Query](https://tanstack.com/query/latest/docs/framework/react/overview) to call your Data Provider (mostly by leveraging TanStack Query's `useQuery` and `useMutation` hooks). You don't need to know TanStack Query to fetch or update data, as react-admin exposes one hook per Data Provider method:
53+
Many react-admin components use the Data Provider: page components like `<List>` and `<Edit>`, reference components like `<ReferenceField>` and `<ReferenceInput>`, action Buttons like `<DeleteButton>` and `<SaveButton>`, and many more.
54+
55+
If you need to call the Data Provider directly from your own components, you can use the specialized hooks provided by react-admin:
5456

5557
* [`useGetList`](./useGetList.md)
5658
* [`useGetOne`](./useGetOne.md)
@@ -98,12 +100,35 @@ const BanUserButton = ({ userId }) => {
98100
};
99101
```
100102

101-
Finally, you can use any of TanStack Query's hooks:
103+
The [Querying the API](./Actions.md) documentation lists all the hooks available for querying the API, as well as the options and return values for each of them.
104+
105+
## React Query
106+
107+
React-admin uses [TanStack Query](https://tanstack.com/query/latest/docs/framework/react/overview) to call the Data Provider. Specialized hooks like `useGetOne` use TanStack Query's hooks under the hood, and accept the same options.
108+
109+
You can use any of TanStack Query's hooks in your own code:
102110

103111
- [`useQuery`](https://tanstack.com/query/latest/docs/framework/react/guides/queries) for reading data
104112
- [`useMutation`](https://tanstack.com/query/latest/docs/framework/react/guides/mutations) for writing data.
105113

106-
The [Querying the API](./Actions.md) documentation lists all the hooks available for querying the API, as well as the options and return values for each of them.
114+
For instance, you can use `useMutation` to call the `dataProvider.update()` directly. This lets you track the mutation's status and add side effects:
115+
116+
```jsx
117+
import { useDataProvider, useNotify } from 'react-admin';
118+
import { useQuery } from '@tanstack/react-query';
119+
120+
const BanUserButton = ({ userId }) => {
121+
const dataProvider = useDataProvider();
122+
const notify = useNotify();
123+
const { mutate, isPending } = useMutation({
124+
mutationFn: () => dataProvider.update('users', { id: userId, data: { isBanned: true } }),
125+
onSuccess: () => notify('User banned'),
126+
});
127+
return <Button label="Ban user" onClick={() => mutate()} disabled={isPending} />;
128+
};
129+
```
130+
131+
Check out the [TanStack Query documentation](https://tanstack.com/query/latest/docs/framework/react/overview) for more information on how to use it.
107132

108133
## Local API Mirror
109134

@@ -320,9 +345,7 @@ Here is a list of react-admin's [relationship components](./Features.md#relation
320345
If a relationship component doesn't fit your specific use case, you can always use a [custom data provider method](#adding-custom-methods) to fetch the required data.
321346
``
322347

323-
## Real-Time Updates And Locks
324-
325-
Teams where several people work in parallel on a common task need to allow live updates, real-time notifications, and prevent data loss when two editors work on the same resource concurrently.
348+
## Realtime
326349

327350
<video controls autoplay playsinline muted>
328351
<source src="./img/CollaborativeDemo.mp4" type="video/mp4" />

docs/SecurityGuide.md

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
---
2+
layout: default
3+
title: "Security"
4+
---
5+
6+
# Security
7+
8+
<video controls autoplay playsinline muted loop>
9+
<source src="./img/login.mp4" type="video/mp4"/>
10+
Your browser does not support the video tag.
11+
</video>
12+
13+
Web applications often need to limit access to specific pages or resources to authenticated users ("**authentication**") and ensure that users can only perform permitted actions ("**authorization**").
14+
15+
React-admin supports both authentication and authorization, allowing you to secure your admin app with your preferred authentication strategy. Since there are many strategies (e.g., OAuth, MFA, passwordless, magic link), react-admin delegates this logic to an `authProvider`.
16+
17+
## The Auth Provider
18+
19+
Authentication and authorization features rely on an **authentication backend** (e.g., OAuth server, API server, or SAML server). The `authProvider` acts as a bridge between react-admin and this authentication backend.
20+
21+
For example, when the user accesses a page component (`<List>`, `<Edit>`, `<Create>`, `<Show>`), react-admin checks if the user is authenticated by calling the `authProvider.checkAuth()` method. If the user is not authenticated, they are redirected to the login page:
22+
23+
```tsx
24+
try {
25+
await authProvider.checkAuth();
26+
} catch (error) {
27+
// The user is not authenticated
28+
return <Redirect to="/login" />;
29+
}
30+
```
31+
32+
If you use JWT tokens, this method checks if the user token is valid and refreshes it if necessary.
33+
34+
An Auth Provider must implement the following methods:
35+
36+
```js
37+
const authProvider = {
38+
// Send username and password to the auth server and get back credentials
39+
async login(params) {/** ... **/},
40+
// Check if an error from the dataProvider indicates an authentication issue
41+
async checkError(error) {/** ... **/},
42+
// Verify that the user's credentials are still valid during navigation
43+
async checkAuth(params) {/** ... **/},
44+
// Remove local credentials and notify the auth server of the logout
45+
async logout() {/** ... **/},
46+
// Retrieve the user's profile
47+
async getIdentity() {/** ... **/},
48+
// (Optional) Check if the user has permission for a specific action on a resource
49+
async canAccess() {/** ... **/},
50+
};
51+
```
52+
53+
You can use an existing Auth Provider from the [List of Available Auth Providers](./AuthProviderList.md) or create your own following the [Building Your Own Auth Provider](./AuthProviderWriting.md) guide.
54+
55+
## Authentication
56+
57+
Once you set an `<Admin authProvider>`, react-admin enables authentication automatically.
58+
59+
```tsx
60+
const App = () => (
61+
<Admin authProvider={authProvider}>
62+
...
63+
</Admin>
64+
);
65+
```
66+
67+
For page components (`<List>`, `<Edit>`, `<Create>`, `<Show>`) and the dashboard, anonymous users are redirected to the login screen. To allow anonymous access on a page, use the `disableAuthentication` prop. For example, in a list view:
68+
69+
```tsx
70+
import { List } from 'react-admin';
71+
72+
const PostList = () => (
73+
<List disableAuthentication>
74+
...
75+
</List>
76+
);
77+
```
78+
79+
For custom routes, anonymous users have access by default. To require authentication on a custom route, wrap the page component in an `<Authenticated>` component:
80+
81+
```tsx
82+
import { Admin, Resource, CustomRoutes, Authenticated } from 'react-admin';
83+
import { Route } from "react-router-dom";
84+
import { MyCustomPage } from './MyCustomPage';
85+
86+
const App = () => (
87+
<Admin>
88+
...
89+
<CustomRoutes>
90+
<Route path="/my-custom-page" element={
91+
<Authenticated>
92+
<MyCustomPage />
93+
</Authenticated>
94+
} />
95+
</CustomRoutes>
96+
</Admin>
97+
);
98+
```
99+
100+
If all your custom routes require authentication, use the `<Admin requireAuth>` prop instead of wrapping each route in `<Authenticated>`:
101+
102+
```tsx
103+
const App = () => (
104+
<Admin
105+
dataProvider={dataProvider}
106+
authProvider={authProvider}
107+
requireAuth
108+
>
109+
...
110+
</Admin>
111+
);
112+
```
113+
114+
Check the [Auth Provider Setup Guide](./Authentication.md) for more details.
115+
116+
## Authorization
117+
118+
After a user is authenticated, your application may need to check if the user has the right to access a specific resource or perform an action.
119+
120+
<video controls autoplay muted loop>
121+
<source src="./img/AccessControl.mp4" type="video/mp4"/>
122+
Your browser does not support the video tag.
123+
</video>
124+
125+
The `authProvider.canAccess()` method determines if the user can access a resource or perform an action. This flexibility allows you to implement various authorization strategies, such as:
126+
127+
- Role-Based Access Control (RBAC)
128+
- Attribute-Based Access Control (ABAC)
129+
- Access Control List (ACL).
130+
131+
Since the auth logic is abstracted by the Auth Provider, you can integrate react-admin with popular authorization solutions like Okta, Casbin, Cerbos, and others.
132+
133+
Page components (`<List>`, `<Create>`, `<Edit>`, `<Show>`) have built-in access control. Before rendering them, react-admin calls `authProvider.canAccess()` with the relevant action and resource parameters.
134+
135+
```tsx
136+
<Resource
137+
name="posts"
138+
// Available if canAccess({ action: 'list', resource: 'posts' }) returns true
139+
list={PostList}
140+
// Available if canAccess({ action: 'create', resource: 'posts' }) returns true
141+
create={PostCreate}
142+
// Available if canAccess({ action: 'edit', resource: 'posts' }) returns true
143+
edit={PostEdit}
144+
// Available if canAccess({ action: 'show', resource: 'posts' }) returns true
145+
show={PostShow}
146+
/>;
147+
```
148+
149+
To control access in your own components, use the `useCanAccess()` hook or the `<CanAccess>` component.
150+
151+
In the following example, only users who can access the `delete` action on the `comments` resource can see the `DeleteCommentButton`:
152+
153+
```tsx
154+
import Stack from '@mui/material/Stack';
155+
import { CanAccess } from 'react-admin';
156+
157+
const CommentsToolbar = ({ record }) => (
158+
<Stack direction="row" spacing={2}>
159+
<ApproveCommentButton record={record} />
160+
<RejectCommentButton record={record} />
161+
<CanAccess action="delete" resource="comments" record={record}>
162+
<DeleteCommentButton record={record} />
163+
</CanAccess>
164+
</Stack>
165+
);
166+
```
167+
168+
Check the [Authorization Guide](./Authorization.md) for more details.
169+
170+
## Login Page
171+
172+
React-admin displays a login page when the user is not authenticated. The login page is a simple form with username and password fields.
173+
174+
![Login form](./img/login-form.png)
175+
176+
You can customize the login page by providing your own component via the `<Admin loginPage>` prop. For example, to use an email field instead of a username field, create a custom login page component:
177+
178+
```tsx
179+
// in src/MyLoginPage.js
180+
import { useState } from 'react';
181+
import { useLogin, useNotify, Notification } from 'react-admin';
182+
183+
const MyLoginPage = ({ theme }) => {
184+
const [email, setEmail] = useState('');
185+
const [password, setPassword] = useState('');
186+
const login = useLogin();
187+
const notify = useNotify();
188+
189+
const handleSubmit = e => {
190+
e.preventDefault();
191+
login({ email, password }).catch(() =>
192+
notify('Invalid email or password')
193+
);
194+
};
195+
196+
return (
197+
<form onSubmit={handleSubmit}>
198+
<input
199+
name="email"
200+
type="email"
201+
value={email}
202+
onChange={e => setEmail(e.target.value)}
203+
/>
204+
<input
205+
name="password"
206+
type="password"
207+
value={password}
208+
onChange={e => setPassword(e.target.value)}
209+
/>
210+
</form>
211+
);
212+
};
213+
214+
export default MyLoginPage;
215+
216+
// in src/App.js
217+
import { Admin } from "react-admin";
218+
import { dataProvider } from "./dataProvider";
219+
import { authProvider } from "./authProvider";
220+
import MyLoginPage from "./MyLoginPage";
221+
222+
const App = () => (
223+
<Admin loginPage={MyLoginPage} authProvider={authProvider} dataProvider={dataProvider}>
224+
...
225+
</Admin>
226+
);
227+
```
228+
229+
You can also disable the `/login` route entirely by passing `false` to this prop. In this case, the `authProvider` must handle redirecting unauthenticated users to a custom login page by returning a `redirectTo` field in response to `checkAuth` (see [`authProvider.checkAuth()`](./AuthProviderWriting.md#checkauth) for details). If you fail to customize the redirection, the app may end up in an infinite loop.
230+
231+
```tsx
232+
const authProvider = {
233+
// ...
234+
async checkAuth() {
235+
// ...
236+
if (!authenticated) {
237+
throw { redirectTo: '/no-access' };
238+
}
239+
},
240+
};
241+
242+
const App = () => (
243+
<Admin authProvider={authProvider} loginPage={false}>
244+
...
245+
</Admin>
246+
);
247+
```
248+
249+
## Calling The Auth Provider
250+
251+
React-admin provides several ways to call authentication provider methods in your components:
252+
253+
- [`useLogin`](./useLogin.md): Calls the `authProvider.login()` method. Use it in custom login screens.
254+
- [`useLogout`](./useLogout.md): Calls the `authProvider.logout()` method. Use it in custom logout buttons.
255+
- [`<Authenticated>`](./Authenticated.md): Redirects to the login page if the user is not authenticated. Use it to protect custom routes.
256+
- [`useAuthState`](./useAuthState.md): Calls the `authProvider.checkAuth()` method. Use it to display different UI elements based on the user's authentication state.
257+
- [`useAuthenticated`](./useAuthenticated.md): Calls the `authProvider.checkAuth()` method and redirect to the login page if the user is not authenticated. Use it to protect custom routes.
258+
- [`useGetIdentity`](./useGetIdentity.md): Calls the `authProvider.getIdentity()` method. Use it to display the user's profile information.
259+
- [`useCanAccess`](./useCanAccess.md): Calls the `authProvider.canAccess()` method. Use it to display different UI elements based on the user's permissions.
260+
- [`<CanAccess>`](./CanAccess.md): Renders its children only of `authProvider.canAccess()` method returns true.
261+
- [`useAuthProvider`](./useAuthProvider.md): Returns the `authProvider` instance. Use it to call other methods of the `authProvider`.
262+

docs/navigation.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<li {% if page.path == 'Architecture.md' %} class="active" {% endif %}><a class="nav-link" href="./Architecture.html">General Concepts</a></li>
2323
<li {% if page.path == 'Features.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./Features.html">Features</a></li>
2424
<li {% if page.path == 'DataFetchingGuide.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./DataFetchingGuide.html">Data Fetching</a></li>
25-
<li {% if page.path == 'Authentication.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./Authentication.html">Security</a></li>
25+
<li {% if page.path == 'SecurityGuide.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./SecurityGuide.html">Security</a></li>
2626
<li {% if page.path == 'ListTutorial.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./ListTutorial.html">CRUD: List</a></li>
2727
<li {% if page.path == 'EditTutorial.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./EditTutorial.html">CRUD: Creation & Edition</a></li>
2828
<li {% if page.path == 'ShowTutorial.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./ShowTutorial.html">CRUD: Show</a></li>
@@ -64,6 +64,7 @@
6464
</ul>
6565

6666
<ul><div>Security</div>
67+
<li {% if page.path == 'Authentication.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./Authentication.html">Auth Provider Setup</a></li>
6768
<li {% if page.path == 'AuthProviderList.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./AuthProviderList.html">Supported backends</a></li>
6869
<li {% if page.path == 'AuthProviderWriting.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./AuthProviderWriting.html">Writing An Auth Provider</a></li>
6970
<li {% if page.path == 'Permissions.md' %} class="active" {% endif %}><a class="nav-link" href="./Permissions.html">Authorization</a></li>

0 commit comments

Comments
 (0)