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 LoginWithEmail to facilitate login with email #10518

Merged
merged 5 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion cypress/support/LoginPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export default url => ({
appLoader: '.app-loader',
username: "input[name='username']",
password: "input[name='password']",
submitButton: 'button',
submitButton: "button[type='submit']",
title: '#react-admin-title',
},

Expand Down
57 changes: 41 additions & 16 deletions docs/Authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ const App = () => (
);
```

An `authProvider` is an object that handles authentication and authorization logic, similar to a `dataProvider`. It exposes methods that react-admin calls when needed, and you can also call these methods manually through specialized hooks.

Once an admin has an `authProvider`, react-admin will restrict CRUD pages (the `list`, `edit`, `create`, and `show` components of your `Resources`) to authenticated users and redirect anonymous users to the `/login` page, displaying a login form for a username and password.

## Anatomy Of An `authProvider`
![Login form](./img/login-form.png)

An `authProvider` is an object that handles authentication and authorization logic, similar to a `dataProvider`. It exposes methods that react-admin calls when needed, and you can also call these methods manually through specialized hooks. The `authProvider` methods must return a Promise.
React-admin offers several built-in `authProvider` implementations for popular authentication services like **Google Identity**, **Microsoft Entra ID**, **AWS Cognito**, **Auth0**, **Keycloak**, and others. Refer to the [List of Available Auth Providers](./AuthProviderList.md) to find one that suits your requirements.

A typical `authProvider` has the following methods:
If you need to implement a custom authentication strategy, the [Building Your Own Auth Provider](./AuthProviderWriting.md) offers a step-by-step guide. It boils down to implementing a few methods that react-admin calls when needed:

```js
const authProvider = {
Expand All @@ -54,8 +56,6 @@ const authProvider = {
};
```

You can use an existing Auth Provider from the [List of Available Auth Providers](./AuthProviderList.md) or write your own by following the [Building Your Own Auth Provider](./AuthProviderWriting.md) instructions.

## Sending Credentials To The API

The `authProvider` handles authentication logic, but the `dataProvider` must include the user credentials in requests to the API.
Expand Down Expand Up @@ -229,23 +229,46 @@ const App = () => (

## Customizing The Login Component

Using an `authProvider` is enough to secure your app if authentication relies on a username and password. But for cases like using an email instead of a username, Single-Sign-On (SSO), or two-factor authentication, you can implement your own `LoginPage` component to be displayed under the `/login` route.

Pass this component to the [`<Admin loginPage>`](./Admin.md#loginpage) prop:
Using an `authProvider` is enough to secure your app if authentication relies on a username and password. But for cases like using an email instead of a username, Single-Sign-On (SSO), or two-factor authentication, you can use a custom login page by setting the [`<Admin loginPage>`](./Admin.md#loginpage) prop.

```jsx
// in src/App.js
import { Admin } from 'react-admin';
For example, to use an email field instead of a username field, use the `LoginWithEmail` component:

import MyLoginPage from './MyLoginPage';
```tsx
import { Admin, LoginWithEmail } from 'react-admin';
import authProvider from './authProvider';

const App = () => (
<Admin loginPage={MyLoginPage} authProvider={authProvider}>
...
<Admin loginPage={LoginWithEmail} authProvider={authProvider}>
...
</Admin>
);
```

![Login with email](./img/LoginWithEmail.jpg)

The default login page component is the `Login` component, which delegates the rendering of the login form to its child, usually a `LoginForm` component. This means you can create a custom login page by adding your own content to the `Login` component.

For instance, to add a "forgot password" link to the login page:

```jsx
import { Box, Link } from '@mui/material';
import { Link as RouterLink } from 'react-router-dom';
import { Login, LoginForm } from 'react-admin';

const MyLogin = () => (
<Login>
<LoginForm />
<Box textAlign="center" mb={1}>
<Link component={RouterLink} to="/forgot-password">
Forgot password?
</Link>
</Box>
</Login>
);
```

![Login with content](./img/LoginWithContent.jpg)

By default, the login page displays a gradient background. To change it, use the default Login component and pass an image URL as the `backgroundImage` prop.

```jsx
Expand All @@ -257,7 +280,9 @@ const MyLoginPage = () => (
);
```

To build a Login page from scratch, use the [`useLogin` hook](./useLogin.md).
![Custom login page](./img/LoginCustomBackground.jpg)

You can also build your login page from scratch, leveraging the [`useLogin` hook](./useLogin.md) to handle the login form submission.

```jsx
// in src/MyLoginPage.js
Expand Down Expand Up @@ -415,7 +440,7 @@ export const dataProvider = addRefreshAuthToDataProvider(baseDataProvider, refre

## Authorization

Access control and permissions allow you to restrict certain pages to specific users. React-admin provides powerful primitives for implementing authorization logic. For detailed guidance, check out the [Authorization](./Permissions.md) documentation.
Access control and permissions allow you to restrict certain pages and features to specific users. React-admin provides powerful primitives for implementing authorization logic. For detailed guidance, check out the [Authorization](./Permissions.md) documentation.

<video controls autoplay muted loop>
<source src="./img/AccessControl.mp4" type="video/mp4"/>
Expand Down
21 changes: 20 additions & 1 deletion docs/CanAccess.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const LogsPage = () => (
);
```

Use the [`<CustomRoutes>`](./CustomRoutes.md) component to add custom routes to your admin.
Use the [`<CustomRoutes>`](./CustomRoutes.md) component to add custom routes to your admin.

```tsx
import { Admin, CustomRoutes, Authenticated, CanAccess, AccessDenied, Layout } from 'react-admin';
Expand Down Expand Up @@ -98,3 +98,22 @@ export const MyMenu = () => (

**Note**: You don't need to use `<Authenticated>` on custom pages if your admin uses [`requireAuth`](./Admin.md#requireauth).

## Access Denied Message

By default, `<CanAccess>` renders nothing when the user doesn't have access to the resource.

On custom pages, it's preferable to show an error message instead. Set the `accessDenied` prop to render a custom component in case of access denial:

```tsx
import { Authenticated, CanAccess, AccessDenied } from 'react-admin';

export const LogsPage = () => (
<Authenticated>
<CanAccess resource="logs" action="read" accessDenied={<AccessDenied />}>
...
</CanAccess>
</Authenticated>
);
```

![Access Denied](./img/accessDenied.png)
14 changes: 8 additions & 6 deletions docs/Permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Once a user is authenticated, your application may need to check if the user has
1. **Access control** relies on `authProvider.canAccess({ resource, action })`, which returns whether the user can access the given resource and action.
2. **Permissions** rely on `authProvider.getPermissions()`, which returns a list of permissions that your components can inspect.

Depending on your needs, you can implement one or the other or both. We recommend Access Control because it allows you to put the authorization logic in the `authProvider` rather than in the code.
Depending on your needs, you can implement one or the other or both. We recommend Access Control because it allows you to put the authorization logic in the `authProvider` rather than in the code.

## Access Control

Expand Down Expand Up @@ -73,7 +73,7 @@ const authProvider = {

### Access Control Strategies

It's your responsibility to implement the `canAccess` method in the `authProvider`. You can implement any access control strategy you want.
It's your responsibility to implement the `canAccess` method in the `authProvider`. You can implement any access control strategy you want.

For example, if the auth backend returns a role at login ('admin', 'user', reader'), you can implement a simple access control strategy as follows:

Expand Down Expand Up @@ -169,6 +169,8 @@ If the `authProvider` doesn't implement the `canAccess` method, react-admin assu

If the current user tries to access a page they don't have access to, they are redirected to an "Access Denied" page. You can customize this page by adding a custom route on the `/accessDenied` path.

![Access Denied](./img/accessDenied.png)

If the `authProvider.canAccess()` method returns an error, the user is redirected to an "Access Control Error" page. You can customize this page by adding a custom route on the `/accessControlError` path.

The **action buttons** (`<EditButton>`, `<CreateButton>`, `<DeleteButton>`, `<ShowButton>`, and `<ListButton>`) also have built-in access control. They are only displayed if the user can access the corresponding action on the resource.
Expand All @@ -194,7 +196,7 @@ The **list components** (`<Datagrid>`), **show components** (`<SimpleShowLayout>

### `useCanAccess`

If you need to control access on mount in your own components, use the `useCanAccess()` hook. Since `authProvider.canAccess()` is asynchronous, the hook returns an object with an `isPending` property set to `true` until the promise resolves. Make sure you don't use the result until `isPending` is `false`.
If you need to control access on mount in your own components, use [the `useCanAccess()` hook](./useCanAccess.md). Since `authProvider.canAccess()` is asynchronous, the hook returns an object with an `isPending` property set to `true` until the promise resolves. Make sure you don't use the result until `isPending` is `false`.

```tsx
import { useCanAccess, DeleteButton } from 'react-admin';
Expand Down Expand Up @@ -234,7 +236,7 @@ const UserList = () => {

### `<CanAccess>`

As an alternative to the `useCanAccess()` hook, you can use the `<CanAccess>` component. It calls `dataProvider.canAccess()` on mount and renders its children only if the user can access the resource and action.
As an alternative to the `useCanAccess()` hook, you can use [the `<CanAccess>` component](./CanAccess.md). It calls `dataProvider.canAccess()` on mount and renders its children only if the user can access the resource and action.

```tsx
import Stack from '@mui/material/Stack';
Expand Down Expand Up @@ -271,7 +273,7 @@ export const LogsPage = () => (
);
```

Use the [`<CustomRoutes>`](./CustomRoutes.md) component to add custom routes to your admin.
Use the [`<CustomRoutes>`](./CustomRoutes.md) component to add custom routes to your admin.

```tsx
import { Admin, CustomRoutes, Authenticated, CanAccess, AccessDenied, Layout } from 'react-admin';
Expand Down Expand Up @@ -330,7 +332,7 @@ Permissions can be stored in various formats:
- an object with fine-grained permissions (e.g. `{ postList: { read: true, write: false, delete: false } }`)
- or even a function

The permissions format is free because react-admin never actually uses the permissions itself. You can use them in your code to hide or display content, redirect the user to another page, or display warnings.
The permissions format is free because react-admin never actually uses the permissions itself. You can use them in your code to hide or display content, redirect the user to another page, or display warnings.

Following is an example where the `authProvider` stores the user's permissions in `localStorage` upon authentication, and returns these permissions when called with `getPermissions`:

Expand Down
3 changes: 3 additions & 0 deletions docs/Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ title: "Index"
* [`<ListLive>`](./ListLive.md)<img class="icon" src="./img/premium.svg" />
* [`<LocalesMenuButton>`](./LocalesMenuButton.md)
* [`<LongForm>`](./LongForm.md)<img class="icon" src="./img/premium.svg" />
* [`<Login>`](./Authentication.md#customizing-the-login-component)
* [`<LoginForm>`](./Authentication.md#customizing-the-login-component)
* [`<LoginWithEmail>`](./Authentication.md#customizing-the-login-component)
* [`<Logout>`](./Authentication.md#customizing-the-logout-component)

**- M -**
Expand Down
24 changes: 19 additions & 5 deletions docs/SecurityGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Once you set an `<Admin authProvider>`, react-admin enables authentication autom
```tsx
const App = () => (
<Admin authProvider={authProvider}>
...
...
</Admin>
);
```
Expand All @@ -84,7 +84,7 @@ import { Route } from "react-router-dom";
import { MyCustomPage } from './MyCustomPage';

const App = () => (
<Admin>
<Admin authProvider={authProvider}>
...
<CustomRoutes>
<Route path="/my-custom-page" element={
Expand Down Expand Up @@ -146,7 +146,7 @@ Page components (`<List>`, `<Create>`, `<Edit>`, `<Show>`) have built-in access
/>;
```

To control access in your own components, use the `useCanAccess()` hook or the `<CanAccess>` component.
To control access in your own components, use the `useCanAccess()` hook or the `<CanAccess>` component.

In the following example, only users who can access the `delete` action on the `comments` resource can see the `DeleteCommentButton`:

Expand All @@ -173,7 +173,22 @@ React-admin displays a login page when the user is not authenticated. The login

![Login form](./img/login-form.png)

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:
You can customize the login page by setting the `<Admin loginPage>` prop.

For example, to use an email field instead of a username field, use the `LoginWithEmail` component:

```tsx
import { Admin, LoginWithEmail } from 'react-admin';
import authProvider from './authProvider';

const App = () => (
<Admin loginPage={LoginWithEmail} authProvider={authProvider}>
...
</Admin>
);
```

If you need other login options (magic link, Email OTP, OAuth provider, etc), you can pass a custom login component, leveraging the `useLogin` hook to call `authProvider.login()`::

```tsx
// in src/MyLoginPage.js
Expand Down Expand Up @@ -259,4 +274,3 @@ React-admin provides several ways to call authentication provider methods in you
- [`useCanAccess`](./useCanAccess.md): Calls the `authProvider.canAccess()` method. Use it to display different UI elements based on the user's permissions.
- [`<CanAccess>`](./CanAccess.md): Renders its children only of `authProvider.canAccess()` method returns true.
- [`useAuthProvider`](./useAuthProvider.md): Returns the `authProvider` instance. Use it to call other methods of the `authProvider`.

Binary file added docs/img/LoginCustomBackground.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/LoginWithContent.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/LoginWithEmail.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading