Skip to content

Commit 1be288a

Browse files
committedOct 11, 2020
Add ability to hide notifications when authProvider.checAuth() fails
Closes #5377
1 parent d2b2adb commit 1be288a

File tree

5 files changed

+126
-6
lines changed

5 files changed

+126
-6
lines changed
 

‎docs/Authentication.md

+78-3
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,11 @@ Note that the `authProvider.logout()` method can return the url to which the use
182182

183183
If the API requires authentication, and the user credentials are missing in the request or invalid, the API usually answers with an HTTP error code 401 or 403.
184184

185-
Fortunately, each time the API returns an error, react-admin calls the `authProvider.checkError()` method. Once again, it's up to you to decide which HTTP status codes should let the user continue (by returning a resolved promise) or log them out (by returning a rejected promise).
185+
Fortunately, each time the API returns an error, react-admin calls the `authProvider.checkError()` method. When `checkError()` returns a rejected promise, react-admin calls the `authProvider.logout()` method.
186186

187-
For instance, to redirect the user to the login page for both 401 and 403 codes:
187+
So it's up to you to decide which HTTP status codes should let the user continue (by returning a resolved promise) or log them out (by returning a rejected promise).
188+
189+
For instance, to log the user out for both 401 and 403 codes:
188190

189191
```js
190192
// in src/authProvider.js
@@ -198,13 +200,54 @@ export default {
198200
localStorage.removeItem('auth');
199201
return Promise.reject();
200202
}
203+
// other error code (404, 500, etc): no need to log out
204+
return Promise.resolve();
205+
},
206+
// ...
207+
};
208+
```
209+
210+
When `authProvider.checkError()` returns a rejected Promise, react-admin redirects to the `/login` page, or to the `error.redirectTo` url. That means you can override the default redirection as follows:
211+
212+
```js
213+
// in src/authProvider.js
214+
export default {
215+
login: ({ username, password }) => { /* ... */ },
216+
getIdentity: () => { /* ... */ },
217+
logout: () => { /* ... */ },
218+
checkError: (error) => {
219+
const status = error.status;
220+
if (status === 401 || status === 403) {
221+
localStorage.removeItem('auth');
222+
return Promise.reject({ redirectTo: '/credentials-required' });
223+
}
224+
// other error code (404, 500, etc): no need to log out
201225
return Promise.resolve();
202226
},
203227
// ...
204228
};
205229
```
206230

207-
Note that when `checkError()` returns a rejected promise, react-admin calls the `authProvider.logout()` method before redirecting, and uses the url which may have been returned by the call to `logout()`.
231+
When `authProvider.checkError()` returns a rejected Promise, react-admin displays a notification to the end user, unlsee the `error.message` is `false`. That means you can disable the notification on error as follows:
232+
233+
```js
234+
// in src/authProvider.js
235+
export default {
236+
login: ({ username, password }) => { /* ... */ },
237+
getIdentity: () => { /* ... */ },
238+
logout: () => { /* ... */ },
239+
checkError: (error) => {
240+
const status = error.status;
241+
if (status === 401 || status === 403) {
242+
localStorage.removeItem('auth');
243+
return Promise.reject({ message: false });
244+
}
245+
// other error code (404, 500, etc): no need to log out
246+
return Promise.resolve();
247+
},
248+
// ...
249+
};
250+
```
208251

209252
## Checking Credentials During Navigation
210253

@@ -246,6 +289,38 @@ export default {
246289

247290
Note that react-admin will call the `authProvider.logout()` method before redirecting. If you specify the `redirectTo` here, it will override the url which may have been returned by the call to `logout()`.
248291

292+
If the promise is rejected, react-admin displays a notification to the end user. You can customize this message by rejecting an error with a `message` property:
293+
294+
```js
295+
// in src/authProvider.js
296+
export default {
297+
login: ({ username, password }) => { /* ... */ },
298+
getIdentity: () => { /* ... */ },
299+
logout: () => { /* ... */ },
300+
checkError: (error) => { /* ... */ },
301+
checkAuth: () => localStorage.getItem('auth')
302+
? Promise.resolve()
303+
: Promise.reject({ message: 'login.required' }), // react-admin passes the error message to the translation layer
304+
// ...
305+
}
306+
```
307+
308+
You can also disable this notification completely by rejecting an error with a `message` with a `false` value:
309+
310+
```js
311+
// in src/authProvider.js
312+
export default {
313+
login: ({ username, password }) => { /* ... */ },
314+
getIdentity: () => { /* ... */ },
315+
logout: () => { /* ... */ },
316+
checkError: (error) => { /* ... */ },
317+
checkAuth: () => localStorage.getItem('auth')
318+
? Promise.resolve()
319+
: Promise.reject({ message: false }),
320+
// ...
321+
}
322+
```
323+
249324
**Tip**: In addition to `login()`, `logout()`, `checkError()`, and `checkAuth()`, react-admin calls the `authProvider.getPermissions()` method to check user permissions. It's useful to enable or disable features on a per user basis. Read the [Authorization Documentation](./Authorization.md) to learn how to implement that type.
250325

251326
## Customizing The Login and Logout Components

‎packages/ra-core/src/auth/useCheckAuth.spec.tsx

+17
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,21 @@ describe('useCheckAuth', () => {
113113
expect(notify).toHaveBeenCalledTimes(0);
114114
expect(queryByText('authenticated')).toBeNull();
115115
});
116+
117+
it('should logout whitout showing a notification when authProvider returns error with message false', async () => {
118+
const { queryByText } = render(
119+
<AuthContext.Provider
120+
value={{
121+
...authProvider,
122+
checkAuth: () => Promise.reject({ message: false }),
123+
}}
124+
>
125+
<TestComponent />
126+
</AuthContext.Provider>
127+
);
128+
await wait();
129+
expect(logout).toHaveBeenCalledTimes(1);
130+
expect(notify).toHaveBeenCalledTimes(0);
131+
expect(queryByText('authenticated')).toBeNull();
132+
});
116133
});

‎packages/ra-core/src/auth/useCheckAuth.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,10 @@ const useCheckAuth = (): CheckAuth => {
6060
? error.redirectTo
6161
: redirectTo
6262
);
63-
!disableNotification &&
63+
const shouldSkipNotify =
64+
disableNotification ||
65+
(error && error.message === false);
66+
!shouldSkipNotify &&
6467
notify(
6568
getErrorMessage(error, 'ra.auth.auth_check_error'),
6669
'warning'
@@ -91,6 +94,7 @@ type CheckAuth = (
9194
params?: any,
9295
logoutOnFailure?: boolean,
9396
redirectTo?: string,
97+
/** @deprecated to disable the notification, authProvider.checkAuth() should return an object with an error property set to true */
9498
disableNotification?: boolean
9599
) => Promise<any>;
96100

‎packages/ra-core/src/auth/useLogoutIfAccessDenied.spec.tsx

+20-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ describe('useLogoutIfAccessDenied', () => {
107107
expect(queryByText('logged in')).toBeNull();
108108
});
109109

110-
it('should logout whitout showing a notification', async () => {
110+
it('should logout whitout showing a notification if disableAuthentication is true', async () => {
111111
const { queryByText } = render(
112112
<AuthContext.Provider value={authProvider}>
113113
<TestComponent
@@ -121,4 +121,23 @@ describe('useLogoutIfAccessDenied', () => {
121121
expect(notify).toHaveBeenCalledTimes(0);
122122
expect(queryByText('logged in')).toBeNull();
123123
});
124+
125+
it('should logout whitout showing a notification if authProvider returns error with message false', async () => {
126+
const { queryByText } = render(
127+
<AuthContext.Provider
128+
value={{
129+
...authProvider,
130+
checkError: () => {
131+
return Promise.reject({ message: false });
132+
},
133+
}}
134+
>
135+
<TestComponent />
136+
</AuthContext.Provider>
137+
);
138+
await wait();
139+
expect(logout).toHaveBeenCalledTimes(1);
140+
expect(notify).toHaveBeenCalledTimes(0);
141+
expect(queryByText('logged in')).toBeNull();
142+
});
124143
});

‎packages/ra-core/src/auth/useLogoutIfAccessDenied.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ const useLogoutIfAccessDenied = (): LogoutIfAccessDenied => {
5959
? error.redirectTo
6060
: undefined;
6161
logout({}, redirectTo);
62-
!disableNotification &&
62+
const shouldSkipNotify =
63+
disableNotification ||
64+
(e && e.message === false) ||
65+
(error && error.message === false);
66+
!shouldSkipNotify &&
6367
notify('ra.notification.logged_out', 'warning');
6468
return true;
6569
})
@@ -89,6 +93,7 @@ const logoutIfAccessDeniedWithoutProvider = () => Promise.resolve(false);
8993
*/
9094
type LogoutIfAccessDenied = (
9195
error?: any,
96+
/** @deprecated to disable the notification, authProvider.checkAuth() should return an object with an error property set to true */
9297
disableNotification?: boolean
9398
) => Promise<boolean>;
9499

0 commit comments

Comments
 (0)
Please sign in to comment.