How do you get access to an Auth0 access token inside React Router v6.4 actions
and loaders
so you can call a secured API?
I've tried to curry the getAccessTokenSilently()
method to the loader but I can't call the useAuth0()
hook outside a component (my routes are created outside a component as recommended by the react router documentation). I can't call useAuth0()
inside the loader for the same reason: it's not a component.
I tried putting the routes inside a component anyway (moving the routes shown in my code below to an <App/>
component) but that results in errors from useAuth0()
about it having to be called from within an Auth0Provider
.
The official Auth0 guide to React Router v6 hasn't been updated to use createBrowserRouter
and doesn't use loaders in their secure API example.
This seems like a fundamental need in any React Router v6.4 application and yet I've never been able to find an example that shows how to do it. Surely someone has done this before?
Update: Thanks to the assistance of Drew this works! The below code is a full working version of this.
Auth0ProviderWithNavigate.tsx
interface Auth0ProviderWithNavigateProps {
children: React.ReactNode;
}
export const Auth0ProviderWithNavigate = ({
children,
}: Auth0ProviderWithNavigateProps) => {
if (
!(
ENV.VITE_AUTH0_DOMAIN &&
ENV.VITE_AUTH0_CLIENT_ID &&
ENV.VITE_AUTH0_CALLBACK_URL
)
) {
return null;
}
return (
<Auth0Provider
domain={ENV.VITE_AUTH0_DOMAIN}
clientId={ENV.VITE_AUTH0_CLIENT_ID}
authorizationParams={{
redirect_uri: ENV.VITE_AUTH0_CALLBACK_URL,
}}
>
{children}
</Auth0Provider>
);
};
AuthenticationGuard.tsx
import { withAuthenticationRequired } from "@auth0/auth0-react";
import { PageLoader } from "./PageLoader";
interface AuthenticationGuardProps {
component: React.ComponentType<object>;
}
export const AuthenticationGuard = ({
component,
}: AuthenticationGuardProps) => {
const Component = withAuthenticationRequired(component, {
onRedirecting: () => <PageLoader />,
});
return <Component />;
};
main.tsx
const root = document.getElementById("root");
root &&
ReactDOM.createRoot(root).render(
<React.StrictMode>
<AppContextProvider>
<Auth0ProviderWithNavigate>
<AppTheme>
<App />
</AppTheme>
</Auth0ProviderWithNavigate>
</AppContextProvider>
</React.StrictMode>
);
App.tsx
const App = () => {
const { getAccessTokenSilently } = useAuth0();
const router = useMemo(() => {
return createBrowserRouter([
{
children: [
{
path: "/",
element: <WelcomePage />,
errorElement: <ErrorPage />,
},
{
children: [
{
id: "edct",
path: "/edct",
element: <AuthenticationGuard component={Edct} />,
errorElement: <ErrorPage />,
loader: AirportCodesLoader({ getAccessTokenSilently }),
},
],
},
],
},
]);
}, [getAccessTokenSilently]);
return <RouterProvider router={router} />;
};
export default App;
AirportCodesLoader.tsx
import { ActionFunction } from "react-router";
const cleanCodes = (codes: string | null): string | null => {
if (!codes) {
return null;
}
return codes
.split(",")
.map((code) => code.trim())
.join(",");
};
interface AppLoader {
getAccessTokenSilently: () => Promise<string>;
}
export const AirportCodesLoader =
({ getAccessTokenSilently }: AppLoader): ActionFunction =>
async ({ request }) => {
const url = new URL(request.url);
console.log(await getAccessTokenSilently());
return {
departureCodes: cleanCodes(url.searchParams.get("departureCodes")) ?? "",
arrivalCodes: cleanCodes(url.searchParams.get("arrivalCodes")) ?? "",
};
};
Yes, while it is the common practice to declare the router configuration outside the React tree, this is just a recommendation. In this case you can create it inside the App
component so it can close over an instance of the auth0
context value to be passed to route loaders. The route loaders that need to receive the auth0
context will need to curry that parameter.
Example:
const AirportCodesLoader = (auth0) => ({ params, request }) => {
... loader logic ...
};
const App = () => {
const auth0 = useAuth0();
const router = React.useMemo(() => { // <-- memoize router reference
return createBrowserRouter([
{
children: [
{
path: "/",
element: <WelcomePage />,
errorElement: <ErrorPage />,
},
{
children: [
{
id: "edct",
path: "/edct",
element: <AuthenticationGuard component={Edct} />,
errorElement: <ErrorPage />,
loader: AirportCodesLoader(auth0), // <-- pass auth0 to loader
},
],
},
],
},
]);
}, [auth0]); // <-- auth0 external dependency
return <RouterProvider router={router} />;
};