ra-auth-auth0 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Readme.md ADDED
@@ -0,0 +1,100 @@
1
+ # ra-auth-auth0
2
+
3
+ An auth provider for [react-admin](https://github.com/marmelab/react-admin) which handles authentication via a [Auth0](https://auth0.com) instance.
4
+
5
+ This package provides:
6
+
7
+ - An `Auth0AuthProvider` for react-admin
8
+
9
+ ## Installation
10
+
11
+ ```sh
12
+ yarn add ra-auth-auth0
13
+ # or
14
+ npm install --save ra-auth-auth0
15
+ ```
16
+
17
+ ## Example usage
18
+
19
+ ```jsx
20
+ // in src/App.tsx
21
+ import React, { useEffect, useRef, useState } from 'react';
22
+ import { Admin, Resource } from 'react-admin';
23
+ import { Auth0AuthProvider } from 'ra-auth-auth0';
24
+ import { Auth0Client } from '@auth0/auth0-spa-js';
25
+ import dataProvider from './dataProvider';
26
+ import posts from './posts';
27
+
28
+ const auth0 = new Auth0Client({
29
+ domain: import.meta.env.VITE_AUTH0_DOMAIN,
30
+ clientId: import.meta.env.VITE_AUTH0_CLIENT_ID,
31
+ cacheLocation: 'localstorage',
32
+ authorizationParams: {
33
+ audience: import.meta.env.VITE_AUTH0_AUDIENCE,
34
+ },
35
+ });
36
+
37
+ const authProvider = Auth0AuthProvider(auth0, {
38
+ loginRedirectUri: import.meta.env.VITE_LOGIN_REDIRECT_URL,
39
+ logoutRedirectUri: import.meta.env.VITE_LOGOUT_REDIRECT_URL,
40
+ });
41
+
42
+ const App = () => {
43
+ return (
44
+ <Admin
45
+ authProvider={authProvider}
46
+ dataProvider={dataProvider}
47
+ >
48
+ <Resource name="posts" {...posts} />
49
+ </Admin>
50
+ );
51
+ };
52
+ export default App;
53
+ ```
54
+
55
+ ## `Auth0AuthProvider` Parameters
56
+
57
+ - `loginRedirectUri` - _optional_ - URI used to override the redirect URI after successful login. Defaults to react-admin `/auth-callback` route.
58
+ - `logoutRedirectUri` - _optional_ - URI used to override the redirect URI after successful logout. Defaults to the current url.
59
+ - `redirectOnCheckAuth` - _optional_ - If set to `true` (the default), redirect users to the Auth0 login page inside the [`checkAuth`](https://marmelab.com/react-admin/AuthProviderWriting.html#checkauth) method. Set it to `false` if you want users to go through a [custom login page](https://marmelab.com/react-admin/Authentication.html#customizing-the-login-component) first.
60
+
61
+ ## Passing The Auth0 Token To Your Backend
62
+
63
+ If you want to pass the Auth0 authentication token to your backend, you can use the [`httpClient`](https://marmelab.com/react-admin/DataProviders.html#adding-custom-headers) exported by this package. For instance, here's how to use it with the [`ra-data-json-server` dataProvider](https://github.com/marmelab/react-admin/tree/master/packages/ra-data-json-server):
64
+
65
+ ```js
66
+ import jsonServerProvider from 'ra-data-json-server';
67
+ import { Auth0Client } from './auth0';
68
+ const dataProvider = jsonServerProvider('http://localhost:3000', httpClient(auth0));
69
+ ```
70
+
71
+ ## Handling Permissions
72
+
73
+ In order to get the users roles directly from Auth0, you have to configure it so that it includes them in the authentication token. This is done by [adding an Action to the login flow](https://auth0.com/docs/manage-users/access-control/sample-use-cases-actions-with-authorization#add-user-roles-to-tokens).
74
+
75
+ The `authProvider` exported by this package will look for a claim that has a name with the term `role` in it and return its value. To change this behavior, override the `getPermissions` method:
76
+
77
+ ```js
78
+ import { Auth0AuthProvider } from 'ra-auth-auth0';
79
+ import { Auth0Client } from './auth0';
80
+
81
+ const authProvider = {
82
+ ...Auth0AuthProvider(Auth0Client),
83
+ async getPermissions() {
84
+ if (!(await client.isAuthenticated())) {
85
+ return;
86
+ }
87
+
88
+ const claims = await client.getIdTokenClaims();
89
+ return claims['https://my-app.example.com/roles'];
90
+ }
91
+ }
92
+ ```
93
+
94
+ ## Demo
95
+
96
+ You can find a working demo, along with the source code, in this project's repository: https://github.com/marmelab/ra-auth-auth0
97
+
98
+ ## License
99
+
100
+ This auth provider is licensed under the MIT License and sponsored by [marmelab](https://marmelab.com).
@@ -0,0 +1,57 @@
1
+ import { AuthProvider } from 'react-admin';
2
+ import { Auth0Client } from '@auth0/auth0-spa-js';
3
+ /**
4
+ * An authProvider which handles authentication via the Auth0 instance.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * import React from 'react';
9
+ * import { Admin, Resource } from 'react-admin';
10
+ * import { Auth0AuthProvider } from 'ra-auth-auth0';
11
+ * import { Auth0Client } from '@auth0/auth0-spa-js';
12
+ * import dataProvider from './dataProvider';
13
+ * import posts from './posts';
14
+ *
15
+ * const clientAuth0 = new Auth0Client({
16
+ * domain: 'your-domain.auth0.com',
17
+ * clientId: 'your-client-id',
18
+ * cacheLocation: 'localstorage',
19
+ * // optional
20
+ * authorizationParams: {
21
+ * audience: 'https://your-domain.auth0.com/api/v2/',
22
+ * },
23
+ * });
24
+ *
25
+ * const authProvider = Auth0AuthProvider(clientAuth0, {
26
+ * loginRedirectUri: 'http://localhost:3000/auth-callback',
27
+ * logoutRedirectUri: 'http://localhost:3000',
28
+ * });
29
+ *
30
+ * const App = () => {
31
+ * return (
32
+ * <Admin
33
+ * authProvider={authProvider}
34
+ * dataProvider={dataProvider}
35
+ * title="Example Admin"
36
+ * >
37
+ * <Resource name="posts" {...posts} />
38
+ * </Admin>
39
+ * );
40
+ * };
41
+ * export default App;
42
+ *
43
+ * ```
44
+ *
45
+ * @param client the Auth0 client
46
+ * @param options The authProvider options
47
+ * @param options.loginRedirectUri The URI to which users should be redirected after they signed in. Must be whitelisted in the Auth0 application settings.
48
+ * @param options.logoutRedirectUri The URI to which users should be redirected after they signed out. Must be whitelisted in the Auth0 application settings.
49
+ * @param options.redirectOnCheckAuth If true, the user will be redirected to Auth0 when checkAuth fails. Defaults to true.
50
+ * @returns an authProvider ready to be used by React-Admin.
51
+ */
52
+ export declare const Auth0AuthProvider: (client: Auth0Client, { loginRedirectUri, logoutRedirectUri, redirectOnCheckAuth, }?: {
53
+ loginRedirectUri?: string;
54
+ logoutRedirectUri?: string;
55
+ redirectOnCheckAuth?: boolean;
56
+ }) => AuthProvider;
57
+ //# sourceMappingURL=authProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authProvider.d.ts","sourceRoot":"","sources":["../src/authProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA8B,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,eAAO,MAAM,iBAAiB,WAClB,WAAW;uBAMI,MAAM;wBACL,MAAM;0BACJ,OAAO;MAIlC,YAmFD,CAAC"}
@@ -0,0 +1,222 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var __generator = (this && this.__generator) || function (thisArg, body) {
11
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
12
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
13
+ function verb(n) { return function (v) { return step([n, v]); }; }
14
+ function step(op) {
15
+ if (f) throw new TypeError("Generator is already executing.");
16
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
17
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
18
+ if (y = 0, t) op = [op[0] & 2, t.value];
19
+ switch (op[0]) {
20
+ case 0: case 1: t = op; break;
21
+ case 4: _.label++; return { value: op[1], done: false };
22
+ case 5: _.label++; y = op[1]; op = [0]; continue;
23
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
24
+ default:
25
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
26
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
27
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
28
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
29
+ if (t[2]) _.ops.pop();
30
+ _.trys.pop(); continue;
31
+ }
32
+ op = body.call(thisArg, _);
33
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
34
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
+ }
36
+ };
37
+ import { PreviousLocationStorageKey } from 'react-admin';
38
+ /**
39
+ * An authProvider which handles authentication via the Auth0 instance.
40
+ *
41
+ * @example
42
+ * ```tsx
43
+ * import React from 'react';
44
+ * import { Admin, Resource } from 'react-admin';
45
+ * import { Auth0AuthProvider } from 'ra-auth-auth0';
46
+ * import { Auth0Client } from '@auth0/auth0-spa-js';
47
+ * import dataProvider from './dataProvider';
48
+ * import posts from './posts';
49
+ *
50
+ * const clientAuth0 = new Auth0Client({
51
+ * domain: 'your-domain.auth0.com',
52
+ * clientId: 'your-client-id',
53
+ * cacheLocation: 'localstorage',
54
+ * // optional
55
+ * authorizationParams: {
56
+ * audience: 'https://your-domain.auth0.com/api/v2/',
57
+ * },
58
+ * });
59
+ *
60
+ * const authProvider = Auth0AuthProvider(clientAuth0, {
61
+ * loginRedirectUri: 'http://localhost:3000/auth-callback',
62
+ * logoutRedirectUri: 'http://localhost:3000',
63
+ * });
64
+ *
65
+ * const App = () => {
66
+ * return (
67
+ * <Admin
68
+ * authProvider={authProvider}
69
+ * dataProvider={dataProvider}
70
+ * title="Example Admin"
71
+ * >
72
+ * <Resource name="posts" {...posts} />
73
+ * </Admin>
74
+ * );
75
+ * };
76
+ * export default App;
77
+ *
78
+ * ```
79
+ *
80
+ * @param client the Auth0 client
81
+ * @param options The authProvider options
82
+ * @param options.loginRedirectUri The URI to which users should be redirected after they signed in. Must be whitelisted in the Auth0 application settings.
83
+ * @param options.logoutRedirectUri The URI to which users should be redirected after they signed out. Must be whitelisted in the Auth0 application settings.
84
+ * @param options.redirectOnCheckAuth If true, the user will be redirected to Auth0 when checkAuth fails. Defaults to true.
85
+ * @returns an authProvider ready to be used by React-Admin.
86
+ */
87
+ export var Auth0AuthProvider = function (client, _a) {
88
+ var _b = _a === void 0 ? {
89
+ redirectOnCheckAuth: true,
90
+ } : _a, loginRedirectUri = _b.loginRedirectUri, logoutRedirectUri = _b.logoutRedirectUri, _c = _b.redirectOnCheckAuth, redirectOnCheckAuth = _c === void 0 ? true : _c;
91
+ return ({
92
+ // Used when the redirection to Auth0 is done from a custom login page
93
+ login: function () {
94
+ return __awaiter(this, void 0, void 0, function () {
95
+ return __generator(this, function (_a) {
96
+ client.loginWithRedirect({
97
+ authorizationParams: {
98
+ redirect_uri: loginRedirectUri !== null && loginRedirectUri !== void 0 ? loginRedirectUri : "".concat(window.location.origin, "/auth-callback"),
99
+ },
100
+ });
101
+ return [2 /*return*/];
102
+ });
103
+ });
104
+ },
105
+ // called when the user clicks on the logout button
106
+ logout: function () {
107
+ return __awaiter(this, void 0, void 0, function () {
108
+ return __generator(this, function (_a) {
109
+ return [2 /*return*/, client.logout({
110
+ logoutParams: {
111
+ returnTo: logoutRedirectUri || window.location.origin,
112
+ },
113
+ })];
114
+ });
115
+ });
116
+ },
117
+ // called when the API returns an error
118
+ checkError: function (_a) {
119
+ var status = _a.status;
120
+ return __awaiter(this, void 0, void 0, function () {
121
+ return __generator(this, function (_b) {
122
+ if (status === 401 || status === 403) {
123
+ throw new Error('Unauthorized');
124
+ }
125
+ return [2 /*return*/];
126
+ });
127
+ });
128
+ },
129
+ // called when the user navigates to a new location, to check for authentication
130
+ checkAuth: function () {
131
+ return __awaiter(this, void 0, void 0, function () {
132
+ var isAuthenticated;
133
+ return __generator(this, function (_a) {
134
+ switch (_a.label) {
135
+ case 0: return [4 /*yield*/, client.isAuthenticated()];
136
+ case 1:
137
+ isAuthenticated = _a.sent();
138
+ if (isAuthenticated) {
139
+ return [2 /*return*/];
140
+ }
141
+ if (redirectOnCheckAuth) {
142
+ localStorage.setItem(PreviousLocationStorageKey, window.location.href);
143
+ client.loginWithRedirect({
144
+ authorizationParams: {
145
+ redirect_uri: loginRedirectUri !== null && loginRedirectUri !== void 0 ? loginRedirectUri : "".concat(window.location.origin, "/auth-callback"),
146
+ },
147
+ });
148
+ }
149
+ return [2 /*return*/];
150
+ }
151
+ });
152
+ });
153
+ },
154
+ // called when the user navigates to a new location, to check for permissions / roles
155
+ getPermissions: function () {
156
+ return __awaiter(this, void 0, void 0, function () {
157
+ var claims, roleProperty;
158
+ return __generator(this, function (_a) {
159
+ switch (_a.label) {
160
+ case 0: return [4 /*yield*/, client.isAuthenticated()];
161
+ case 1:
162
+ if (!(_a.sent())) {
163
+ return [2 /*return*/];
164
+ }
165
+ return [4 /*yield*/, client.getIdTokenClaims()];
166
+ case 2:
167
+ claims = _a.sent();
168
+ roleProperty = Object.keys(claims).find(function (key) {
169
+ return key.includes('role');
170
+ });
171
+ return [2 /*return*/, claims[roleProperty]];
172
+ }
173
+ });
174
+ });
175
+ },
176
+ getIdentity: function () {
177
+ return __awaiter(this, void 0, void 0, function () {
178
+ var user;
179
+ return __generator(this, function (_a) {
180
+ switch (_a.label) {
181
+ case 0: return [4 /*yield*/, client.isAuthenticated()];
182
+ case 1:
183
+ if (!_a.sent()) return [3 /*break*/, 3];
184
+ return [4 /*yield*/, client.getUser()];
185
+ case 2:
186
+ user = _a.sent();
187
+ return [2 /*return*/, {
188
+ id: user.email,
189
+ fullName: user.name,
190
+ avatar: user.picture,
191
+ }];
192
+ case 3: throw new Error('Failed to get identity.');
193
+ }
194
+ });
195
+ });
196
+ },
197
+ handleCallback: function () {
198
+ return __awaiter(this, void 0, void 0, function () {
199
+ var query, error_1;
200
+ return __generator(this, function (_a) {
201
+ switch (_a.label) {
202
+ case 0:
203
+ query = window.location.search;
204
+ if (!(query.includes('code=') && query.includes('state='))) return [3 /*break*/, 4];
205
+ _a.label = 1;
206
+ case 1:
207
+ _a.trys.push([1, 3, , 4]);
208
+ return [4 /*yield*/, client.handleRedirectCallback()];
209
+ case 2:
210
+ _a.sent();
211
+ return [2 /*return*/];
212
+ case 3:
213
+ error_1 = _a.sent();
214
+ console.log('error', error_1);
215
+ throw error_1;
216
+ case 4: throw new Error('Failed to handle login callback.');
217
+ }
218
+ });
219
+ });
220
+ },
221
+ });
222
+ };
@@ -0,0 +1,20 @@
1
+ import { fetchUtils } from 'react-admin';
2
+ /**
3
+ * An httpClient that adds authentication headers needed by Auth0 in all requests.
4
+ * @param auth0Client the Auth0 client.
5
+ * @returns a function with the same definition as `httpClient` that adds an `Authorization` header containing the Auth0 token.
6
+ */
7
+ export declare const httpClient: (auth0Client: any) => (url: any, options: fetchUtils.Options | undefined) => Promise<{
8
+ status: number;
9
+ headers: Headers;
10
+ body: string;
11
+ json: any;
12
+ }>;
13
+ /**
14
+ * Return the headers needed by Auth0.
15
+ * @param token the Auth0 token
16
+ * @param options the fetch options (so that we do not override other headers)
17
+ * @returns the headers needed by Auth0
18
+ */
19
+ export declare const getAuth0Headers: (token: string | null, options: fetchUtils.Options | undefined) => Headers;
20
+ //# sourceMappingURL=httpClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"httpClient.d.ts","sourceRoot":"","sources":["../src/httpClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;GAIG;AACH,eAAO,MAAM,UAAU,gBAAiB,GAAG,WAClC,GAAG,WACC,WAAW,OAAO,GAAG,SAAS;;;;;EAQ1C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,UACjB,MAAM,GAAG,IAAI,WACX,WAAW,OAAO,GAAG,SAAS,KACxC,OASF,CAAC"}
@@ -0,0 +1,81 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
13
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
14
+ return new (P || (P = Promise))(function (resolve, reject) {
15
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
16
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
17
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
18
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
19
+ });
20
+ };
21
+ var __generator = (this && this.__generator) || function (thisArg, body) {
22
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
23
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
24
+ function verb(n) { return function (v) { return step([n, v]); }; }
25
+ function step(op) {
26
+ if (f) throw new TypeError("Generator is already executing.");
27
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
28
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
29
+ if (y = 0, t) op = [op[0] & 2, t.value];
30
+ switch (op[0]) {
31
+ case 0: case 1: t = op; break;
32
+ case 4: _.label++; return { value: op[1], done: false };
33
+ case 5: _.label++; y = op[1]; op = [0]; continue;
34
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
35
+ default:
36
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
37
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
38
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
39
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
40
+ if (t[2]) _.ops.pop();
41
+ _.trys.pop(); continue;
42
+ }
43
+ op = body.call(thisArg, _);
44
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
45
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
46
+ }
47
+ };
48
+ import { fetchUtils } from 'react-admin';
49
+ /**
50
+ * An httpClient that adds authentication headers needed by Auth0 in all requests.
51
+ * @param auth0Client the Auth0 client.
52
+ * @returns a function with the same definition as `httpClient` that adds an `Authorization` header containing the Auth0 token.
53
+ */
54
+ export var httpClient = function (auth0Client) { return function (url, options) { return __awaiter(void 0, void 0, void 0, function () {
55
+ var token, requestHeaders;
56
+ return __generator(this, function (_a) {
57
+ switch (_a.label) {
58
+ case 0: return [4 /*yield*/, auth0Client.getTokenSilently()];
59
+ case 1:
60
+ token = _a.sent();
61
+ requestHeaders = getAuth0Headers(token, options);
62
+ return [2 /*return*/, fetchUtils.fetchJson(url, __assign(__assign({}, options), { headers: requestHeaders }))];
63
+ }
64
+ });
65
+ }); }; };
66
+ /**
67
+ * Return the headers needed by Auth0.
68
+ * @param token the Auth0 token
69
+ * @param options the fetch options (so that we do not override other headers)
70
+ * @returns the headers needed by Auth0
71
+ */
72
+ export var getAuth0Headers = function (token, options) {
73
+ var headers = ((options && options.headers) ||
74
+ new Headers({
75
+ Accept: 'application/json',
76
+ }));
77
+ if (token) {
78
+ headers.set('Authorization', "Bearer ".concat(token));
79
+ }
80
+ return headers;
81
+ };
package/esm/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './authProvider';
2
+ export * from './httpClient';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC"}
package/esm/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './authProvider';
2
+ export * from './httpClient';
@@ -0,0 +1,57 @@
1
+ import { AuthProvider } from 'react-admin';
2
+ import { Auth0Client } from '@auth0/auth0-spa-js';
3
+ /**
4
+ * An authProvider which handles authentication via the Auth0 instance.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * import React from 'react';
9
+ * import { Admin, Resource } from 'react-admin';
10
+ * import { Auth0AuthProvider } from 'ra-auth-auth0';
11
+ * import { Auth0Client } from '@auth0/auth0-spa-js';
12
+ * import dataProvider from './dataProvider';
13
+ * import posts from './posts';
14
+ *
15
+ * const clientAuth0 = new Auth0Client({
16
+ * domain: 'your-domain.auth0.com',
17
+ * clientId: 'your-client-id',
18
+ * cacheLocation: 'localstorage',
19
+ * // optional
20
+ * authorizationParams: {
21
+ * audience: 'https://your-domain.auth0.com/api/v2/',
22
+ * },
23
+ * });
24
+ *
25
+ * const authProvider = Auth0AuthProvider(clientAuth0, {
26
+ * loginRedirectUri: 'http://localhost:3000/auth-callback',
27
+ * logoutRedirectUri: 'http://localhost:3000',
28
+ * });
29
+ *
30
+ * const App = () => {
31
+ * return (
32
+ * <Admin
33
+ * authProvider={authProvider}
34
+ * dataProvider={dataProvider}
35
+ * title="Example Admin"
36
+ * >
37
+ * <Resource name="posts" {...posts} />
38
+ * </Admin>
39
+ * );
40
+ * };
41
+ * export default App;
42
+ *
43
+ * ```
44
+ *
45
+ * @param client the Auth0 client
46
+ * @param options The authProvider options
47
+ * @param options.loginRedirectUri The URI to which users should be redirected after they signed in. Must be whitelisted in the Auth0 application settings.
48
+ * @param options.logoutRedirectUri The URI to which users should be redirected after they signed out. Must be whitelisted in the Auth0 application settings.
49
+ * @param options.redirectOnCheckAuth If true, the user will be redirected to Auth0 when checkAuth fails. Defaults to true.
50
+ * @returns an authProvider ready to be used by React-Admin.
51
+ */
52
+ export declare const Auth0AuthProvider: (client: Auth0Client, { loginRedirectUri, logoutRedirectUri, redirectOnCheckAuth, }?: {
53
+ loginRedirectUri?: string;
54
+ logoutRedirectUri?: string;
55
+ redirectOnCheckAuth?: boolean;
56
+ }) => AuthProvider;
57
+ //# sourceMappingURL=authProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authProvider.d.ts","sourceRoot":"","sources":["../src/authProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA8B,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,eAAO,MAAM,iBAAiB,WAClB,WAAW;uBAMI,MAAM;wBACL,MAAM;0BACJ,OAAO;MAIlC,YAmFD,CAAC"}
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.Auth0AuthProvider = void 0;
40
+ var react_admin_1 = require("react-admin");
41
+ /**
42
+ * An authProvider which handles authentication via the Auth0 instance.
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * import React from 'react';
47
+ * import { Admin, Resource } from 'react-admin';
48
+ * import { Auth0AuthProvider } from 'ra-auth-auth0';
49
+ * import { Auth0Client } from '@auth0/auth0-spa-js';
50
+ * import dataProvider from './dataProvider';
51
+ * import posts from './posts';
52
+ *
53
+ * const clientAuth0 = new Auth0Client({
54
+ * domain: 'your-domain.auth0.com',
55
+ * clientId: 'your-client-id',
56
+ * cacheLocation: 'localstorage',
57
+ * // optional
58
+ * authorizationParams: {
59
+ * audience: 'https://your-domain.auth0.com/api/v2/',
60
+ * },
61
+ * });
62
+ *
63
+ * const authProvider = Auth0AuthProvider(clientAuth0, {
64
+ * loginRedirectUri: 'http://localhost:3000/auth-callback',
65
+ * logoutRedirectUri: 'http://localhost:3000',
66
+ * });
67
+ *
68
+ * const App = () => {
69
+ * return (
70
+ * <Admin
71
+ * authProvider={authProvider}
72
+ * dataProvider={dataProvider}
73
+ * title="Example Admin"
74
+ * >
75
+ * <Resource name="posts" {...posts} />
76
+ * </Admin>
77
+ * );
78
+ * };
79
+ * export default App;
80
+ *
81
+ * ```
82
+ *
83
+ * @param client the Auth0 client
84
+ * @param options The authProvider options
85
+ * @param options.loginRedirectUri The URI to which users should be redirected after they signed in. Must be whitelisted in the Auth0 application settings.
86
+ * @param options.logoutRedirectUri The URI to which users should be redirected after they signed out. Must be whitelisted in the Auth0 application settings.
87
+ * @param options.redirectOnCheckAuth If true, the user will be redirected to Auth0 when checkAuth fails. Defaults to true.
88
+ * @returns an authProvider ready to be used by React-Admin.
89
+ */
90
+ var Auth0AuthProvider = function (client, _a) {
91
+ var _b = _a === void 0 ? {
92
+ redirectOnCheckAuth: true,
93
+ } : _a, loginRedirectUri = _b.loginRedirectUri, logoutRedirectUri = _b.logoutRedirectUri, _c = _b.redirectOnCheckAuth, redirectOnCheckAuth = _c === void 0 ? true : _c;
94
+ return ({
95
+ // Used when the redirection to Auth0 is done from a custom login page
96
+ login: function () {
97
+ return __awaiter(this, void 0, void 0, function () {
98
+ return __generator(this, function (_a) {
99
+ client.loginWithRedirect({
100
+ authorizationParams: {
101
+ redirect_uri: loginRedirectUri !== null && loginRedirectUri !== void 0 ? loginRedirectUri : "".concat(window.location.origin, "/auth-callback"),
102
+ },
103
+ });
104
+ return [2 /*return*/];
105
+ });
106
+ });
107
+ },
108
+ // called when the user clicks on the logout button
109
+ logout: function () {
110
+ return __awaiter(this, void 0, void 0, function () {
111
+ return __generator(this, function (_a) {
112
+ return [2 /*return*/, client.logout({
113
+ logoutParams: {
114
+ returnTo: logoutRedirectUri || window.location.origin,
115
+ },
116
+ })];
117
+ });
118
+ });
119
+ },
120
+ // called when the API returns an error
121
+ checkError: function (_a) {
122
+ var status = _a.status;
123
+ return __awaiter(this, void 0, void 0, function () {
124
+ return __generator(this, function (_b) {
125
+ if (status === 401 || status === 403) {
126
+ throw new Error('Unauthorized');
127
+ }
128
+ return [2 /*return*/];
129
+ });
130
+ });
131
+ },
132
+ // called when the user navigates to a new location, to check for authentication
133
+ checkAuth: function () {
134
+ return __awaiter(this, void 0, void 0, function () {
135
+ var isAuthenticated;
136
+ return __generator(this, function (_a) {
137
+ switch (_a.label) {
138
+ case 0: return [4 /*yield*/, client.isAuthenticated()];
139
+ case 1:
140
+ isAuthenticated = _a.sent();
141
+ if (isAuthenticated) {
142
+ return [2 /*return*/];
143
+ }
144
+ if (redirectOnCheckAuth) {
145
+ localStorage.setItem(react_admin_1.PreviousLocationStorageKey, window.location.href);
146
+ client.loginWithRedirect({
147
+ authorizationParams: {
148
+ redirect_uri: loginRedirectUri !== null && loginRedirectUri !== void 0 ? loginRedirectUri : "".concat(window.location.origin, "/auth-callback"),
149
+ },
150
+ });
151
+ }
152
+ return [2 /*return*/];
153
+ }
154
+ });
155
+ });
156
+ },
157
+ // called when the user navigates to a new location, to check for permissions / roles
158
+ getPermissions: function () {
159
+ return __awaiter(this, void 0, void 0, function () {
160
+ var claims, roleProperty;
161
+ return __generator(this, function (_a) {
162
+ switch (_a.label) {
163
+ case 0: return [4 /*yield*/, client.isAuthenticated()];
164
+ case 1:
165
+ if (!(_a.sent())) {
166
+ return [2 /*return*/];
167
+ }
168
+ return [4 /*yield*/, client.getIdTokenClaims()];
169
+ case 2:
170
+ claims = _a.sent();
171
+ roleProperty = Object.keys(claims).find(function (key) {
172
+ return key.includes('role');
173
+ });
174
+ return [2 /*return*/, claims[roleProperty]];
175
+ }
176
+ });
177
+ });
178
+ },
179
+ getIdentity: function () {
180
+ return __awaiter(this, void 0, void 0, function () {
181
+ var user;
182
+ return __generator(this, function (_a) {
183
+ switch (_a.label) {
184
+ case 0: return [4 /*yield*/, client.isAuthenticated()];
185
+ case 1:
186
+ if (!_a.sent()) return [3 /*break*/, 3];
187
+ return [4 /*yield*/, client.getUser()];
188
+ case 2:
189
+ user = _a.sent();
190
+ return [2 /*return*/, {
191
+ id: user.email,
192
+ fullName: user.name,
193
+ avatar: user.picture,
194
+ }];
195
+ case 3: throw new Error('Failed to get identity.');
196
+ }
197
+ });
198
+ });
199
+ },
200
+ handleCallback: function () {
201
+ return __awaiter(this, void 0, void 0, function () {
202
+ var query, error_1;
203
+ return __generator(this, function (_a) {
204
+ switch (_a.label) {
205
+ case 0:
206
+ query = window.location.search;
207
+ if (!(query.includes('code=') && query.includes('state='))) return [3 /*break*/, 4];
208
+ _a.label = 1;
209
+ case 1:
210
+ _a.trys.push([1, 3, , 4]);
211
+ return [4 /*yield*/, client.handleRedirectCallback()];
212
+ case 2:
213
+ _a.sent();
214
+ return [2 /*return*/];
215
+ case 3:
216
+ error_1 = _a.sent();
217
+ console.log('error', error_1);
218
+ throw error_1;
219
+ case 4: throw new Error('Failed to handle login callback.');
220
+ }
221
+ });
222
+ });
223
+ },
224
+ });
225
+ };
226
+ exports.Auth0AuthProvider = Auth0AuthProvider;
@@ -0,0 +1,20 @@
1
+ import { fetchUtils } from 'react-admin';
2
+ /**
3
+ * An httpClient that adds authentication headers needed by Auth0 in all requests.
4
+ * @param auth0Client the Auth0 client.
5
+ * @returns a function with the same definition as `httpClient` that adds an `Authorization` header containing the Auth0 token.
6
+ */
7
+ export declare const httpClient: (auth0Client: any) => (url: any, options: fetchUtils.Options | undefined) => Promise<{
8
+ status: number;
9
+ headers: Headers;
10
+ body: string;
11
+ json: any;
12
+ }>;
13
+ /**
14
+ * Return the headers needed by Auth0.
15
+ * @param token the Auth0 token
16
+ * @param options the fetch options (so that we do not override other headers)
17
+ * @returns the headers needed by Auth0
18
+ */
19
+ export declare const getAuth0Headers: (token: string | null, options: fetchUtils.Options | undefined) => Headers;
20
+ //# sourceMappingURL=httpClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"httpClient.d.ts","sourceRoot":"","sources":["../src/httpClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;GAIG;AACH,eAAO,MAAM,UAAU,gBAAiB,GAAG,WAClC,GAAG,WACC,WAAW,OAAO,GAAG,SAAS;;;;;EAQ1C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,UACjB,MAAM,GAAG,IAAI,WACX,WAAW,OAAO,GAAG,SAAS,KACxC,OASF,CAAC"}
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
+ return new (P || (P = Promise))(function (resolve, reject) {
16
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
20
+ });
21
+ };
22
+ var __generator = (this && this.__generator) || function (thisArg, body) {
23
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
24
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
25
+ function verb(n) { return function (v) { return step([n, v]); }; }
26
+ function step(op) {
27
+ if (f) throw new TypeError("Generator is already executing.");
28
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
29
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
30
+ if (y = 0, t) op = [op[0] & 2, t.value];
31
+ switch (op[0]) {
32
+ case 0: case 1: t = op; break;
33
+ case 4: _.label++; return { value: op[1], done: false };
34
+ case 5: _.label++; y = op[1]; op = [0]; continue;
35
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
36
+ default:
37
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
38
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
39
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
40
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
41
+ if (t[2]) _.ops.pop();
42
+ _.trys.pop(); continue;
43
+ }
44
+ op = body.call(thisArg, _);
45
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
46
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
+ }
48
+ };
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.getAuth0Headers = exports.httpClient = void 0;
51
+ var react_admin_1 = require("react-admin");
52
+ /**
53
+ * An httpClient that adds authentication headers needed by Auth0 in all requests.
54
+ * @param auth0Client the Auth0 client.
55
+ * @returns a function with the same definition as `httpClient` that adds an `Authorization` header containing the Auth0 token.
56
+ */
57
+ var httpClient = function (auth0Client) { return function (url, options) { return __awaiter(void 0, void 0, void 0, function () {
58
+ var token, requestHeaders;
59
+ return __generator(this, function (_a) {
60
+ switch (_a.label) {
61
+ case 0: return [4 /*yield*/, auth0Client.getTokenSilently()];
62
+ case 1:
63
+ token = _a.sent();
64
+ requestHeaders = (0, exports.getAuth0Headers)(token, options);
65
+ return [2 /*return*/, react_admin_1.fetchUtils.fetchJson(url, __assign(__assign({}, options), { headers: requestHeaders }))];
66
+ }
67
+ });
68
+ }); }; };
69
+ exports.httpClient = httpClient;
70
+ /**
71
+ * Return the headers needed by Auth0.
72
+ * @param token the Auth0 token
73
+ * @param options the fetch options (so that we do not override other headers)
74
+ * @returns the headers needed by Auth0
75
+ */
76
+ var getAuth0Headers = function (token, options) {
77
+ var headers = ((options && options.headers) ||
78
+ new Headers({
79
+ Accept: 'application/json',
80
+ }));
81
+ if (token) {
82
+ headers.set('Authorization', "Bearer ".concat(token));
83
+ }
84
+ return headers;
85
+ };
86
+ exports.getAuth0Headers = getAuth0Headers;
package/lib/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './authProvider';
2
+ export * from './httpClient';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC"}
package/lib/index.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./authProvider"), exports);
18
+ __exportStar(require("./httpClient"), exports);
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "ra-auth-auth0",
3
+ "version": "1.0.0",
4
+ "repository": "git@github.com:marmelab/ra-auth-auth0.git",
5
+ "author": "Anthony Rimet <anthony@marmelab.com> (https://marmelab.com/)",
6
+ "license": "MIT",
7
+ "files": [
8
+ "*.md",
9
+ "lib",
10
+ "esm",
11
+ "src"
12
+ ],
13
+ "main": "lib/index",
14
+ "module": "esm/index.js",
15
+ "types": "esm/index.d.ts",
16
+ "sideEffects": false,
17
+ "peerDependencies": {
18
+ "@auth0/auth0-spa-js": "^2.0.0",
19
+ "ra-core": "^4.7.0",
20
+ "react": "^16.9.0 || ^17.0.0 || ^18.0.0",
21
+ "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0"
22
+ },
23
+ "dependencies": {
24
+ "react-admin": "^4.7.0"
25
+ },
26
+ "scripts": {
27
+ "build": "yarn run build-cjs && yarn run build-esm",
28
+ "build-cjs": "rimraf ./lib && tsc",
29
+ "build-esm": "rimraf ./esm && tsc --outDir esm --module es2015",
30
+ "watch": "tsc --outDir esm --module es2015 --watch",
31
+ "lint": "eslint --fix ./src",
32
+ "test-unit": "jest"
33
+ },
34
+ "devDependencies": {
35
+ "@types/jest": "^29.1.0",
36
+ "jest": "^29.1.1",
37
+ "jest-environment-jsdom": "^29.1.1",
38
+ "react": "^18.2.0",
39
+ "react-dom": "^18.2.0",
40
+ "rimraf": "^3.0.2",
41
+ "ts-jest": "^29.0.3",
42
+ "typescript": "^4.8.4"
43
+ },
44
+ "jest": {
45
+ "preset": "ts-jest",
46
+ "testEnvironment": "jsdom",
47
+ "collectCoverageFrom": [
48
+ "**/*.js",
49
+ "**/*.ts",
50
+ "!__tests__/util.ts",
51
+ "!coverage/**",
52
+ "!**/node_modules/**"
53
+ ],
54
+ "verbose": true,
55
+ "automock": false,
56
+ "clearMocks": true,
57
+ "testPathIgnorePatterns": [
58
+ "/node_modules/"
59
+ ],
60
+ "moduleFileExtensions": [
61
+ "ts",
62
+ "tsx",
63
+ "js",
64
+ "jsx",
65
+ "json",
66
+ "node"
67
+ ]
68
+ }
69
+ }
@@ -0,0 +1,149 @@
1
+ import { AuthProvider, PreviousLocationStorageKey } from 'react-admin';
2
+ import { Auth0Client } from '@auth0/auth0-spa-js';
3
+
4
+ /**
5
+ * An authProvider which handles authentication via the Auth0 instance.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import React from 'react';
10
+ * import { Admin, Resource } from 'react-admin';
11
+ * import { Auth0AuthProvider } from 'ra-auth-auth0';
12
+ * import { Auth0Client } from '@auth0/auth0-spa-js';
13
+ * import dataProvider from './dataProvider';
14
+ * import posts from './posts';
15
+ *
16
+ * const clientAuth0 = new Auth0Client({
17
+ * domain: 'your-domain.auth0.com',
18
+ * clientId: 'your-client-id',
19
+ * cacheLocation: 'localstorage',
20
+ * // optional
21
+ * authorizationParams: {
22
+ * audience: 'https://your-domain.auth0.com/api/v2/',
23
+ * },
24
+ * });
25
+ *
26
+ * const authProvider = Auth0AuthProvider(clientAuth0, {
27
+ * loginRedirectUri: 'http://localhost:3000/auth-callback',
28
+ * logoutRedirectUri: 'http://localhost:3000',
29
+ * });
30
+ *
31
+ * const App = () => {
32
+ * return (
33
+ * <Admin
34
+ * authProvider={authProvider}
35
+ * dataProvider={dataProvider}
36
+ * title="Example Admin"
37
+ * >
38
+ * <Resource name="posts" {...posts} />
39
+ * </Admin>
40
+ * );
41
+ * };
42
+ * export default App;
43
+ *
44
+ * ```
45
+ *
46
+ * @param client the Auth0 client
47
+ * @param options The authProvider options
48
+ * @param options.loginRedirectUri The URI to which users should be redirected after they signed in. Must be whitelisted in the Auth0 application settings.
49
+ * @param options.logoutRedirectUri The URI to which users should be redirected after they signed out. Must be whitelisted in the Auth0 application settings.
50
+ * @param options.redirectOnCheckAuth If true, the user will be redirected to Auth0 when checkAuth fails. Defaults to true.
51
+ * @returns an authProvider ready to be used by React-Admin.
52
+ */
53
+ export const Auth0AuthProvider = (
54
+ client: Auth0Client,
55
+ {
56
+ loginRedirectUri,
57
+ logoutRedirectUri,
58
+ redirectOnCheckAuth = true,
59
+ }: {
60
+ loginRedirectUri?: string;
61
+ logoutRedirectUri?: string;
62
+ redirectOnCheckAuth?: boolean;
63
+ } = {
64
+ redirectOnCheckAuth: true,
65
+ }
66
+ ): AuthProvider => ({
67
+ // Used when the redirection to Auth0 is done from a custom login page
68
+ async login() {
69
+ client.loginWithRedirect({
70
+ authorizationParams: {
71
+ redirect_uri:
72
+ loginRedirectUri ??
73
+ `${window.location.origin}/auth-callback`,
74
+ },
75
+ });
76
+ },
77
+ // called when the user clicks on the logout button
78
+ async logout() {
79
+ return client.logout({
80
+ logoutParams: {
81
+ returnTo: logoutRedirectUri || window.location.origin,
82
+ },
83
+ });
84
+ },
85
+ // called when the API returns an error
86
+ async checkError({ status }) {
87
+ if (status === 401 || status === 403) {
88
+ throw new Error('Unauthorized');
89
+ }
90
+ },
91
+ // called when the user navigates to a new location, to check for authentication
92
+ async checkAuth() {
93
+ const isAuthenticated = await client.isAuthenticated();
94
+ if (isAuthenticated) {
95
+ return;
96
+ }
97
+
98
+ if (redirectOnCheckAuth) {
99
+ localStorage.setItem(
100
+ PreviousLocationStorageKey,
101
+ window.location.href
102
+ );
103
+ client.loginWithRedirect({
104
+ authorizationParams: {
105
+ redirect_uri:
106
+ loginRedirectUri ??
107
+ `${window.location.origin}/auth-callback`,
108
+ },
109
+ });
110
+ }
111
+ },
112
+ // called when the user navigates to a new location, to check for permissions / roles
113
+ async getPermissions() {
114
+ if (!(await client.isAuthenticated())) {
115
+ return;
116
+ }
117
+
118
+ // If Auth0 instance contains rules for returning permissions, use them
119
+ const claims = await client.getIdTokenClaims();
120
+ const roleProperty = Object.keys(claims).find(key =>
121
+ key.includes('role')
122
+ );
123
+ return claims[roleProperty];
124
+ },
125
+ async getIdentity() {
126
+ if (await client.isAuthenticated()) {
127
+ const user = await client.getUser();
128
+ return {
129
+ id: user.email,
130
+ fullName: user.name,
131
+ avatar: user.picture,
132
+ };
133
+ }
134
+ throw new Error('Failed to get identity.');
135
+ },
136
+ async handleCallback() {
137
+ const query = window.location.search;
138
+ if (query.includes('code=') && query.includes('state=')) {
139
+ try {
140
+ await client.handleRedirectCallback();
141
+ return;
142
+ } catch (error) {
143
+ console.log('error', error);
144
+ throw error;
145
+ }
146
+ }
147
+ throw new Error('Failed to handle login callback.');
148
+ },
149
+ });
@@ -0,0 +1,38 @@
1
+ import { fetchUtils } from 'react-admin';
2
+
3
+ /**
4
+ * An httpClient that adds authentication headers needed by Auth0 in all requests.
5
+ * @param auth0Client the Auth0 client.
6
+ * @returns a function with the same definition as `httpClient` that adds an `Authorization` header containing the Auth0 token.
7
+ */
8
+ export const httpClient = (auth0Client: any) => async (
9
+ url: any,
10
+ options: fetchUtils.Options | undefined
11
+ ) => {
12
+ const token = await auth0Client.getTokenSilently();
13
+ const requestHeaders = getAuth0Headers(token, options);
14
+ return fetchUtils.fetchJson(url, {
15
+ ...options,
16
+ headers: requestHeaders,
17
+ });
18
+ };
19
+
20
+ /**
21
+ * Return the headers needed by Auth0.
22
+ * @param token the Auth0 token
23
+ * @param options the fetch options (so that we do not override other headers)
24
+ * @returns the headers needed by Auth0
25
+ */
26
+ export const getAuth0Headers = (
27
+ token: string | null,
28
+ options: fetchUtils.Options | undefined
29
+ ): Headers => {
30
+ const headers = ((options && options.headers) ||
31
+ new Headers({
32
+ Accept: 'application/json',
33
+ })) as Headers;
34
+ if (token) {
35
+ headers.set('Authorization', `Bearer ${token}`);
36
+ }
37
+ return headers;
38
+ };
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './authProvider';
2
+ export * from './httpClient';