|
| 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 | + |
| 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 | + |
0 commit comments