Issue
Dear fellow React developers.
Can someone explain or show what should be the best approach whilst using MSAL in conjunction with React Router in its current latest version 6.4.1 when you want to use the new loader feature?
Based on the new loader feature that you can read about here:
"Each route can define a "loader" function to provide data to the route element before it renders."
https://reactrouter.com/en/main/route/loader
The question arises in me because I'm lacking in imagination here... the only way I can recall to have access to the msal instance is by using the hooks the MSAL library provides when on a component inside the MsalProvider. However, the new loader feature relies on a plain function where I can't use hooks or access any application state.
Any help will be very welcome. Thanks in advance.
To give a little context below I share some of my application code.
in my UserEditScreen.tsx:
// UserEditScreen component definition
....
// loader definition outside UserEditScreen component
export const loader = ({ request, params }: LoaderFunctionArgs) => {
...
// some more code to clarify my need on the msal instance inside the loader
const request = {
account: msalInstance.getActiveAccount(),
scopes: scopes
}
const accessToken = await msalInstance.acquireTokenSilent(request)
.then(response => response.accessToken)
.catch(error => {
console.error(error);
})
// then with the accessToken I can just use made
// a javascript fetch request to my secured endpoint in azure
...
}
in my app.tsx
type AppProps = {
msalInstance: PublicClientApplication;
}
const router = createBrowserRouter(createRoutesFromElements(
<Route path="/" errorElement={<GlobalError />}>
...
<Route path="/administration" element={
<AuthenticatedScreen>
<AdministrationScreen />
</AuthenticatedScreen>
}>
<Route path='users' element={<UsersScreen />} />
<Route path='users/new' element={<UserNewScreen />} />
<Route path='users/:id/edit'
element={<UserEditScreen />}
loader={userLoader}
errorElement={<UserEditError />}
/>
</Route>
...
</Route>
));
function App({ msalInstance }: AppProps) {
return (
<MsalProvider instance={msalInstance}>
<RouterProvider router={router} />
</MsalProvider>
);
}
export default App;
in my index.tsx
const msalInstance = new PublicClientApplication(msalConfig);
const accounts = msalInstance.getAllAccounts();
if (accounts.length > 0) {
msalInstance.setActiveAccount(accounts[0]);
}
msalInstance.addEventCallback((event: EventMessage) => {
if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) {
const payload = event.payload as AuthenticationResult;
const account = payload.account;
msalInstance.setActiveAccount(account);
}
});
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App msalInstance={msalInstance} />
</React.StrictMode>
);
Solution
Instead of creating the router component you could convert it to a function that takes the msalInstance
as an argument to close it over in scope and returns the configured router.
Example:
App
type AppProps = {
msalInstance: PublicClientApplication;
}
interface CreateRouter {
msalInstance: PublicClientApplication;
}
const createRouter = ({ msalInstance }: CreateRouter) => createBrowserRouter(
createRoutesFromElements(
<Route path="/" errorElement={<GlobalError />}>
...
<Route path="/administration" element={
<AuthenticatedScreen>
<AdministrationScreen />
</AuthenticatedScreen>
}>
<Route path='users' element={<UsersScreen />} />
<Route path='users/new' element={<UserNewScreen />} />
<Route path='users/:id/edit'
element={<UserEditScreen />}
loader={userLoader(msalInstance)}
errorElement={<UserEditError />}
/>
</Route>
...
</Route>
)
);
function App({ msalInstance }: AppProps) {
const router = React.useMemo(
() => createRouter({ msalInstance }),
[msalInstance]
);
return (
<MsalProvider instance={msalInstance}>
<RouterProvider router={router} />
</MsalProvider>
);
}
export default App;
UserEditScreen
Similarly the loader
function can be updated to a curried function that takes the msalInstance
object as an argument and returns the loader function with the msalInstance
object closed over in scope.
// loader definition outside UserEditScreen component
export const loader = (msalInstance: PublicClientApplication) =>
({ request, params }: LoaderFunctionArgs) => {
...
// some more code to clarify my need on the msal instance inside the loader
const request = {
account: msalInstance.getActiveAccount(),
scopes: scopes
}
const accessToken = await msalInstance.acquireTokenSilent(request)
.then(response => response.accessToken)
.catch(error => {
console.error(error);
})
// then with the accessToken I can just use made
// a javascript fetch request to my secured endpoint in azure
...
};
Answered By - Drew Reese
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.