Skip to content

Commit

Permalink
[Doc] Improve instructions for setting up a redirection url for third…
Browse files Browse the repository at this point in the history
…-party auth

Refs marmelab#8457
  • Loading branch information
fzaninotto authored and JinParc committed Dec 19, 2022
1 parent 3f60199 commit 52100e6
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 76 deletions.
10 changes: 7 additions & 3 deletions docs/Admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,9 @@ See The [Authentication documentation](./Authentication.md#customizing-the-login

## `authCallbackPage`

Used for external authentication services callbacks, the `AuthCallback` page can be customized by passing a component of your own as the `authCallbackPage` prop. React-admin will display this component whenever the `/auth-callback` route is called.
React-admin apps contain a special route called `/auth-callback` to let external authentication providers (like Auth0, Cognito, OIDC servers) redirect users after login. This route renders the `AuthCallback` component by default, which in turn calls `authProvider.handleCallback`.

If you need a different behavior for this route, you can render a custom component by passing it as the `authCallbackPage` prop.

```jsx
import MyAuthCallbackPage from './MyAuthCallbackPage';
Expand All @@ -456,9 +458,11 @@ const App = () => (
);
```

You can also disable it completely along with the `/auth-callback` route by passing `false` to this prop.
**Note**: You should seldom use this option, even when using an external authentication provider. Since you can already define the `/auth-callback` route controller via `authProvider.handleCallback`, the `authCallbackPage` prop is only useful when you need the user's feedback after they logged in.

You can also disable the `/auth-callback` route altogether by passing `authCallbackPage={false}`.

See The [Authentication documentation](./Authentication.md#handling-external-authentication-services-callbacks) for more details.
See The [Authentication documentation](./Authentication.md#using-external-authentication-providers) for more details.

## ~~`history`~~

Expand Down
55 changes: 35 additions & 20 deletions docs/AuthProviderWriting.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const authProvider = {
checkAuth: params => Promise.resolve(/* ... */),
logout: () => Promise.resolve(/* ... */),
getIdentity: () => Promise.resolve(/* ... */),
authCallback: () => Promise.resolve(/* ... */), // for third-party authentication only
// authorization
getPermissions: () => Promise.resolve(/* ... */),
};
Expand Down Expand Up @@ -397,50 +398,64 @@ React-admin doesn't use permissions by default, but it provides [the `usePermiss
### `handleCallback`
This is used when integrating a third party authentication service such as [Auth0](https://auth0.com/). React-admin provides a route at the `/auth-callback` path you can configure as the callback in the authentication service. After logging in using the authentication service page, users will be redirected to this page. The `/auth-callback` route will then call the AuthProvider `handleCallback` method where you can validate users are indeed authenticated.
This method is used when integrating a third-party authentication providers such as [Auth0](https://auth0.com/). React-admin provides a route at the `/auth-callback` path, to be used as the callback URL in the authentication service. After logging in using the authentication service, users will be redirected to this route. The `/auth-callback` route calls the `authProvider.handleCallback` method on mount.
**Tip**: if you want to redirect users to the page they were on before logging in, you can store the location in localStorage under the key provided by the `PreviousLocationStorageKey` constant.
So `handleCallback` lets you process query parameters passed by the third-party authentication service, e.g. to retrieve an authentication token.
Here's an example using Auth0:
```js
import { Auth0Client } from './Auth0Client';
```jsx
import { PreviousLocationStorageKey } from 'react-admin';
import { Auth0Client } from './Auth0Client';

export const authProvider = {
async login() => { /* Nothing to do here, this function will never be called */ },
async checkAuth() {
const isAuthenticated = await client.isAuthenticated();
if (isAuthenticated) {
return;
}

// not authenticated: save the location that the user tried to access
localStorage.setItem(PreviousLocationStorageKey, window.location.href);

// then redirect the user to the Auth0 service
client.loginWithRedirect({
authorizationParams: {
// after login, Auth0 will redirect users back to this page
redirect_uri: `${window.location.origin}/auth-callback`,
},
});
},
// A user logged in successfully on the Auth0 service
// and was redirected back to the /auth-callback route on the app
async handleCallback() {
const query = window.location.search;
// If we did receive the Auth0 parameters
if (query.includes('code=') && query.includes('state=')) {
try {
// Request the Auth0 client to validate them
await Auth0Client.handleRedirectCallback();
return;
} catch (error) {
console.log('error', error);
throw error;
}
if (!query.includes('code=') && ¡query.includes('state=')) {
throw new Error('Failed to handle login callback.');
}
throw new Error('Failed to handle login callback.');
// If we did receive the Auth0 parameters,
// get an access token based on the query paramaters
await Auth0Client.handleRedirectCallback();
},
...
}
```
Once `handleCallback` returns a resolved Promise, react-admin redirects the user to the home page, or to the location found in `localStorage.getItem(PreviousLocationStorageKey)`. In the above example, `authProvider.checkAuth()` sets this location to the page the user was trying to access.
You can override this behavior by returning an object with a `redirectTo` property, as follows:
```jsx
async handleCallback() {
if (!query.includes('code=') && ¡query.includes('state=')) {
throw new Error('Failed to handle login callback.');
}
// If we did receive the Auth0 parameters,
// get an access token based on the query paramaters
await Auth0Client.handleRedirectCallback();
return { redirectTo: '/posts' };
},
```
## Request Format
React-admin calls the `authProvider` methods with the following params:
Expand All @@ -452,8 +467,8 @@ React-admin calls the `authProvider` methods with the following params:
| `checkAuth` | Check credentials before moving to a new route | `Object` whatever params passed to `useCheckAuth()` - empty for react-admin default routes |
| `logout` | Log a user out | |
| `getIdentity` | Get the current user identity | |
| `getPermissions` | Get the current user credentials | `Object` whatever params passed to `usePermissions()` - empty for react-admin default routes |
| `handleCallback` | Validate users after third party authentication service redirection | |
| `getPermissions` | Get the current user credentials | `Object` whatever params passed to `usePermissions()` - empty for react-admin default routes |
## Response Format
Expand All @@ -466,8 +481,8 @@ React-admin calls the `authProvider` methods with the following params:
| `checkAuth` | User is authenticated | `void` |
| `logout` | Auth backend acknowledged logout | `string | false | void` route to redirect to after logout, defaults to `/login` |
| `getIdentity` | Auth backend returned identity | `{ id: string | number, fullName?: string, avatar?: string }` |
| `getPermissions` | Auth backend returned permissions | `Object | Array` free format - the response will be returned when `usePermissions()` is called |
| `handleCallback` | User is authenticated | `void | { redirectTo?: string | boolean }` route to redirect to after login |
| `getPermissions` | Auth backend returned permissions | `Object | Array` free format - the response will be returned when `usePermissions()` is called |
## Error Format
Expand All @@ -480,6 +495,6 @@ When the auth backend returns an error, the Auth Provider should return a reject
| `checkAuth` | User is not authenticated | `void | { redirectTo?: string, message?: string }` route to redirect to after logout, message to notify the user |
| `logout` | Auth backend failed to log the user out | `void` |
| `getIdentity` | Auth backend failed to return identity | `Object` free format - returned as `error` when `useGetIdentity()` is called |
| `getPermissions` | Auth backend failed to return permissions | `Object` free format - returned as `error` when `usePermissions()` is called |
| `handleCallback` | Failed to authenticate users after redirection | `void | { redirectTo?: string, logoutOnFailure?: boolean, message?: string }` |
| `getPermissions` | Auth backend failed to return permissions | `Object` free format - returned as `error` when `usePermissions()` is called |
113 changes: 60 additions & 53 deletions docs/Authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,59 +82,6 @@ Now the admin is secured: The user can be authenticated and use their credential

If you have a custom REST client, don't forget to add credentials yourself.

## Handling External Authentication Services Callbacks

When using external authentication services such as those implementing OAuth, you usually need a callback route. React-admin provides a default one at `/auth-callback`. It will call the `AuthProvider.handleCallback` method that may validate the params received from the URL and redirect users to any page (the home page by default) afterwards.

It's up to you to decide when to redirect users to the third party authentication service, for instance:
* Directly in the `AuthProvider.checkAuth` method when users are not yet authenticated;
* When users click a button on a [custom login page](#customizing-the-login-component)

For instance, here's what a simple authProvider for Auth0 might look like:

```js
import { Auth0Client } from './Auth0Client';

export const authProvider = {
async checkAuth() {
const isAuthenticated = await Auth0Client.isAuthenticated();
if (isAuthenticated) {
return;
}

Auth0Client.loginWithRedirect({
authorizationParams: {
redirect_uri: `${window.location.origin}/auth-callback`,
},
});
},
async handleCallback() {
const query = window.location.search;
if (query.includes('code=') && query.includes('state=')) {
try {
await Auth0Client.handleRedirectCallback();
return;
} catch (error) {
console.log('error', error);
throw error;
}
}
throw new Error('Failed to handle login callback.');
},
async logout() {
const isAuthenticated = await client.isAuthenticated();
if (isAuthenticated) {
// need to check for this as react-admin calls logout in case checkAuth failed
return client.logout({
returnTo: window.location.origin,
});
}
},
async login() => { /* Nothing to do here, this function will never be called */ },
...
}
```

## Allowing Anonymous Access

As long as you add an `authProvider`, react-admin restricts access to all the pages declared in the `<Resource>` components. If you want to allow anonymous access, you can set the `disableAuthentication` prop in the page components.
Expand Down Expand Up @@ -225,6 +172,66 @@ const App = () => (
);
```

## Using External Authentication Providers

Instead of the built-in Login page, you can opt for an external authentication provider, such as Auth0, Cognito, or any other OAuth-based service. These services all require a callback URL in the app, to redirect users after login.

React-admin provides a default callback URL at `/auth-callback`. This route calls the `authProvider.handleCallback` method on mount. This means it's the `authProvider`'s job to use the params received from the callback URL to authenticate future API calls.

For instance, here's what a simple authProvider for Auth0 might look like:

```js
import { Auth0Client } from './Auth0Client';

export const authProvider = {
async login() => { /* Nothing to do here, this function will never be called */ },
async checkAuth() {
const isAuthenticated = await Auth0Client.isAuthenticated();
if (isAuthenticated) {
return;
}
// not authenticated: redirect the user to the Auth0 service,
// where they will be redirected back to the app after login
Auth0Client.loginWithRedirect({
authorizationParams: {
redirect_uri: `${window.location.origin}/auth-callback`,
},
});
},
// A user logged successfully on the Auth0 service
// and was redirected back to the /auth-callback route on the app
async handleCallback() {
const query = window.location.search;
if (query.includes('code=') && query.includes('state=')) {
try {
// get an access token based on the query paramaters
await Auth0Client.handleRedirectCallback();
return;
} catch (error) {
console.log('error', error);
throw error;
}
}
throw new Error('Failed to handle login callback.');
},
async logout() {
const isAuthenticated = await client.isAuthenticated();
// need to check for this as react-admin calls logout in case checkAuth failed
if (isAuthenticated) {
return client.logout({
returnTo: window.location.origin,
});
}
},
...
}
```

It's up to you to decide when to redirect users to the third party authentication service, for instance:

* Directly in the `AuthProvider.checkAuth()` method as above;
* When users click a button on a [custom login page](#customizing-the-login-component)

## Customizing The Login Component

Using `authProvider` is enough to implement a full-featured authorization system if the authentication relies on a username and password.
Expand Down

0 comments on commit 52100e6

Please sign in to comment.