There are a few things unclear to a new user of such a library... maybe this issue could be used to clear some things up (i.e. some additions required in docs to smooth things over)
Answer: I provided an answer to the problem I had getting started with this library in the comment below
I looked for a discussion section (please let me know if there is a better place for this message)...
I might just be missing something super obvious.
I started by following the instructions, and I get the sample app working beautifully.
But when I try to cherry-pick the relevant parts to a new project, no matter what I try it doesn't work. Console errors usually include :
BrowserAuthError: no_account_error: No account object provided to acquireTokenSilent and no active account has been set. Please call setActiveAccount or provide an account on the request.
Uncaught (in promise) ClientConfigurationError: empty_url_error: URL was empty or null.
I also tried the much more basic implementation as in your docs, but similar issues.
----------------------------- vrmt/ui/package.json -----------------------------
index ce5eca0..79b523c 100644
@@ -10,6 +10,7 @@
},
"dependencies": {
"@promitheus/ra-data-postgrest": "^1.2.2",
+ "ra-auth-msal": "^1.0.0",
"react": "^18.2.0",
"react-admin": "^4.9.0",
"react-dom": "^18.2.0"
----------------------------- vrmt/ui/src/App.tsx -----------------------------
index 76ef1a5..4247452 100644
@@ -1,6 +1,19 @@
-import { Admin, Resource, fetchUtils } from "react-admin";
+import React from "react";
+import { Admin, Resource } from "react-admin";
// import { ListGuesser } from "react-admin";
-import postgrestRestProvider from '@promitheus/ra-data-postgrest';
+import postgrestRestProvider from "@promitheus/ra-data-postgrest";
+import { PublicClientApplication } from "@azure/msal-browser";
+import { LoginPage, msalAuthProvider, msalHttpClient } from "ra-auth-msal";
+import { BrowserRouter } from "react-router-dom";
+import {
+ getPermissionsFromAccount,
+ loginRequest,
+ msalConfig,
+ tokenRequest,
+} from "./authConfig";
+
+import { CustomLoginPage } from "./CustomLoginPage";
+import Layout from "./Layout";
import { ActivityList } from './sections/activities';
import { AoEList } from './sections/areas_of_expertise';
@@ -9,38 +22,66 @@ import { EmployeeList } from './sections/employees';
import { ProjectList } from './sections/projects';
import { TimeTrackingTaskList } from './sections/time_tracking_tasks';
-const httpClient = (url, options = {}) => {
- if (!options.headers) {
- options.headers = new Headers({ Accept: 'application/json' });
- }
- // add your own headers here
- options.headers.set('Authorization', 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidnJtdF9wb3dlciJ9.fOzb_9hhUa2YzyMgM_o6_p_9B75BOWwX5R6cGk3y8Tc');
- return fetchUtils.fetchJson(url, options);
-};
+const redirectOnCheckAuth = true;
+
+const myMSALObj = new PublicClientApplication(msalConfig);
+
+const App = () => {
+ const authProvider = msalAuthProvider({
+ msalInstance: myMSALObj,
+ loginRequest,
+ tokenRequest,
+ getPermissionsFromAccount,
+ redirectOnCheckAuth,
+ });
-const dataProvider = postgrestRestProvider(
- 'http://localhost:3000',
- httpClient,
- 'eq',
- new Map([
+ const httpClient = msalHttpClient({
+ msalInstance: myMSALObj,
+ tokenRequest,
+ });
+
+ const dataProvider = postgrestRestProvider(
+ 'http://localhost:3000',
+ httpClient,
+ 'eq',
+ new Map([
['activities', ['activity_id']],
['areas_of_expertise', ['aoe_id']],
['areas_of_work', ['aow_id']],
['employees', ['employee_id']],
['projects', ['project_id']],
['time_tracking_tasks', ['time_tracking_task_id']],
- ])
-);
-
-const App = () => (
- <Admin dataProvider={dataProvider} title="VRMT">
- <Resource name="employees" list={EmployeeList} />
- <Resource name="activities" list={ActivityList} />
- <Resource name="areas_of_expertise" list={AoEList} />
- <Resource name="areas_of_work" list={AoWList} />
- <Resource name="projects" list={ProjectList} />
- <Resource name="time_tracking_tasks" list={TimeTrackingTaskList} />
- </Admin>
-);
+ ])
+ );
+
+ return (
+ <BrowserRouter>
+ <Admin
+ authProvider={authProvider}
+ dataProvider={dataProvider}
+ title="VRMT"
+ layout={Layout}
+ loginPage={redirectOnCheckAuth ? LoginPage : CustomLoginPage}
+ >
+ {(permissions) => (
+ <>
+ <Resource name="activities" list={ActivityList} />
+ <Resource name="areas_of_expertise" list={AoEList} />
+ <Resource name="areas_of_work" list={AoWList} />
+ <Resource name="projects" list={ProjectList} />
+ <Resource name="time_tracking_tasks" list={TimeTrackingTaskList} />
+ {permissions ? (
+ <>
+ {permissions.includes("admin") ? (
+ <Resource name="employees" {...EmployeeList} />
+ ) : null}
+ </>
+ ) : null}
+ </>
+ )}
+ </Admin>
+ </BrowserRouter>
+ );
+};
export default App;
----------------------- vrmt/ui/src/CustomLoginPage.tsx -----------------------
new file mode 100644
index 0000000..79a8b29
@@ -0,0 +1,76 @@
+import {
+ Box,
+ Button,
+ Card,
+ CardActions,
+ CardContent,
+ CircularProgress,
+ Typography,
+} from "@mui/material";
+import * as React from "react";
+import { useLogin, useNotify, useSafeSetState } from "react-admin";
+
+/**
+ * Csutom Login Page used to trigger the redirection to the MS login page.
+ */
+export const CustomLoginPage = () => {
+ const login = useLogin();
+ const [loading, setLoading] = useSafeSetState(false);
+ const notify = useNotify();
+
+ const submit = () => {
+ setLoading(true);
+ login({})
+ .then(() => {
+ setLoading(false);
+ })
+ .catch((error) => {
+ setLoading(false);
+ const errorMsg =
+ typeof error === "string"
+ ? error
+ : error && error.message
+ ? error.message
+ : undefined;
+ notify(errorMsg, {
+ type: "error",
+ messageArgs: {
+ _: errorMsg,
+ },
+ });
+ });
+ };
+
+ return (
+ <Box
+ component="form"
+ onSubmit={submit}
+ display="flex"
+ justifyContent="center"
+ >
+ <Card sx={{ width: 300, m: 8 }}>
+ <CardContent>
+ <Typography variant="h5" component="div" textAlign="center">
+ Custom login page
+ </Typography>
+ <CardActions>
+ <Button
+ variant="contained"
+ type="submit"
+ color="primary"
+ disabled={loading}
+ fullWidth
+ sx={{ mt: 2 }}
+ >
+ {loading ? (
+ <CircularProgress size={19} thickness={3} sx={{ m: 0.3 }} />
+ ) : (
+ "Sign in with Microsoft"
+ )}
+ </Button>
+ </CardActions>
+ </CardContent>
+ </Card>
+ </Box>
+ );
+};
---------------------------- vrmt/ui/src/Layout.tsx ----------------------------
new file mode 100644
index 0000000..22cacde
@@ -0,0 +1,16 @@
+import * as React from 'react';
+
+import { ReactQueryDevtools } from 'react-query/devtools';
+import { Layout } from 'react-admin';
+import { CssBaseline } from '@mui/material';
+
+export default props => (
+ <>
+ <CssBaseline />
+ <Layout {...props} />
+ <ReactQueryDevtools
+ initialIsOpen={false}
+ toggleButtonProps={{ style: { width: 20, height: 30 } }}
+ />
+ </>
+);
-------------------------- vrmt/ui/src/authConfig.ts --------------------------
new file mode 100644
index 0000000..cd9a069
@@ -0,0 +1,65 @@
+import {
+ Configuration,
+ RedirectRequest,
+ SilentRequest,
+ AccountInfo,
+ } from "@azure/msal-browser";
+
+ /**
+ * Configuration object to be passed to MSAL instance on creation.
+ * For a full list of MSAL.js configuration parameters, visit:
+ * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/configuration.md
+ */
+ export const msalConfig: Configuration = {
+ auth: {
+ // 'Application (client) ID' of app registration in Azure portal - this value is a GUID
+ clientId: import.meta.env.VITE_MSAL_CLIENT_ID,
+ // Full directory URL, in the form of https://login.microsoftonline.com/<tenant-id>
+ authority: import.meta.env.VITE_MSAL_AUTHORITY,
+ // Full redirect URL, in form of http://localhost:8080/auth-callback
+ redirectUri: `${import.meta.env.VITE_APP_BASE_URI}/auth-callback`,
+ // We need to disable this feature because it is already handled by react-admin, and would otherwise conflict
+ navigateToLoginRequestUrl: false,
+ },
+ cache: {
+ cacheLocation: "sessionStorage", // This configures where your cache will be stored
+ storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
+ },
+ };
+
+ /**
+ * Scopes you add here will be prompted for user consent during sign-in.
+ * By default, MSAL.js will add OIDC scopes (openid, profile, email) to any login request.
+ * For more information about OIDC scopes, visit:
+ * https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes
+ */
+ export const loginRequest: RedirectRequest = {
+ scopes: ["User.Read"],
+ };
+
+ /**
+ * Add here the scopes to request when obtaining an access token for MS Graph API. For more information, see:
+ * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md
+ */
+ export const tokenRequest: SilentRequest = {
+ scopes: ["User.Read"],
+ forceRefresh: false, // Set this to "true" to skip a cached token and go to the server to get a new token
+ };
+
+ /**
+ * Customize this map to match your own roles and permissions
+ */
+ const rolesPermissionMap = {
+ "363472b9-7c21-466c-be1a-d9151b6ad21c": "user",
+ "9bb5f9e0-0746-405b-8494-01be926009c5": "admin",
+ };
+
+ /**
+ * Custom function to map roles to permissions, using the rolesPermissionMap above.
+ * Alternatively, you can use the MS Graph API to get more information about the user's roles and groups.
+ */
+ export const getPermissionsFromAccount = async (account: AccountInfo) => {
+ const roles = account?.idTokenClaims?.roles ?? [];
+ return roles.map((role) => rolesPermissionMap[role]);
+ };
+
\ No newline at end of file
------------------------------ vrmt/ui/yarn.lock ------------------------------
index f1ffa6b..c6a1860 100644
@@ -10,6 +10,18 @@
"@jridgewell/gen-mapping" "^0.1.0"
"@jridgewell/trace-mapping" "^0.3.9"
+"@azure/msal-browser@^2.16.1":
+ version "2.34.0"
+ resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-2.34.0.tgz#e2aa497e74bfa3480c7f03cccd57244e8ab7de00"
+ integrity sha512-stoXdlfAtyVIMOp1lS5PorgO5f66MGRi3Q1FBlXhVZFTsTfAWrNdSOx1m/PXWHskWE9aXO+NEzXVOoWmDNnvNA==
+ dependencies:
+ "@azure/msal-common" "^11.0.0"
+
+"@azure/msal-common@^11.0.0":
+ version "11.0.0"
+ resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-11.0.0.tgz#d35bfa6cdd2a5b8c036ce427aa3fd36f8f985239"
+ integrity sha512-SZH8ObQ3Hq5v3ogVGBYJp1nNW7p+MtM4PH4wfNadBP9wf7K0beQHF9iOtRcjPOkwZf+ZD49oXqw91LndIkdk8g==
+
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a"
@@ -1505,6 +1517,14 @@ query-string@^7.1.1:
split-on-first "^1.0.0"
strict-uri-encode "^2.0.0"
+ra-auth-msal@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/ra-auth-msal/-/ra-auth-msal-1.0.0.tgz#0941ca5e9778b6713ca886a9bfa427c2eb28f6d0"
+ integrity sha512-60MM9YDB9l4u7Gpbqcb+R+hbJfdMZs1N8wvtpgZjmLXoailByxU1TXUDXz2OEJSV6hl+yy3xbJbPJgkFbmXvSw==
+ dependencies:
+ "@azure/msal-browser" "^2.16.1"
+ react-admin "^4.7.0"
+
ra-core@^4.9.0:
version "4.9.0"
resolved "https://registry.yarnpkg.com/ra-core/-/ra-core-4.9.0.tgz#763d7f2eb2696ea6f27c4e1d33c4eb29646b34af"
@@ -1555,7 +1575,7 @@ ra-ui-materialui@^4.9.0:
react-query "^3.32.1"
react-transition-group "^4.4.1"
-react-admin@^4.9.0:
+react-admin@^4.7.0, react-admin@^4.9.0:
version "4.9.0"
resolved "https://registry.yarnpkg.com/react-admin/-/react-admin-4.9.0.tgz#da8be76a527c506f71259a2858c58d01c0443d3e"
integrity sha512-h7JI/zVZObXEU0z2+KPM7PXbmNkl2VczfpXnbMK1m3UJ5T1KdJml3xOp0phYHNV30DjJV9tqiwOkQ9t3cNMviw==