T
TanStackโ€ข13mo ago
flat-fuchsia

[RACE CONDITION] Auth0 Redirect and Context

Is there a way I can put my Auth0 State into my Router Context and be able to use useNavigate() in order to redirect back to routes a user is trying to access? I'm trying to use useAuth0 and useNavigate but RouterProvider is waiting for its context from useAuth0 and useNavigate that is being used inside of the Auth0ProviderWithRedirect needs to be within RouterProvider Here is their example with react-router https://github.com/auth0/auth0-react/blob/main/EXAMPLES.md#protecting-a-route-in-a-react-router-dom-v6-app Any extra pair of ๐Ÿ‘€ would be helpful
function Auth0ProviderWithRedirect({ children, ...props }: Auth0ProviderWithRedirectProps) {
const navigate = useNavigate(); // <-- Needed to redirect a user back to a page they are trying to navigate to while unauthorized
const onRedirectCallback: Auth0ProviderOptions['onRedirectCallback'] = (
appState,
) => {
navigate({ to: appState?.returnTo || window.location.pathname });
};

return (
<Auth0Provider {...props} onRedirectCallback={onRedirectCallback}>
{children}
</Auth0Provider>
);
}
function Auth0ProviderWithRedirect({ children, ...props }: Auth0ProviderWithRedirectProps) {
const navigate = useNavigate(); // <-- Needed to redirect a user back to a page they are trying to navigate to while unauthorized
const onRedirectCallback: Auth0ProviderOptions['onRedirectCallback'] = (
appState,
) => {
navigate({ to: appState?.returnTo || window.location.pathname });
};

return (
<Auth0Provider {...props} onRedirectCallback={onRedirectCallback}>
{children}
</Auth0Provider>
);
}
function InnerApp() {
const auth = useAuth0(); // <-- needed to populate my context
return <RouterProvider router={router} context={{ auth }} />;
}

function App() {
return (
<Auth0ProviderWithRedirect
domain={...}
clientId={...}
authorizationParams={{
redirect_uri: '...',
audience: '...',
}}
>
<InnerApp />
</Auth0ProviderWithRedirect>
);
}
function InnerApp() {
const auth = useAuth0(); // <-- needed to populate my context
return <RouterProvider router={router} context={{ auth }} />;
}

function App() {
return (
<Auth0ProviderWithRedirect
domain={...}
clientId={...}
authorizationParams={{
redirect_uri: '...',
audience: '...',
}}
>
<InnerApp />
</Auth0ProviderWithRedirect>
);
}
GitHub
auth0-react/EXAMPLES.md at main ยท auth0/auth0-react
Auth0 SDK for React Single Page Applications (SPA) - auth0/auth0-react
35 Replies
genetic-orange
genetic-orangeโ€ข13mo ago
you could use router.navigate() inside the auth0 provider
flat-fuchsia
flat-fuchsiaOPโ€ข13mo ago
Thank you @Manuel Schiller @Manuel Schiller It seems when I do it with router.navigate() the auth inside of my context goes undefined for a moment and crashes my _auth route
const onRedirectCallback: Auth0ProviderOptions['onRedirectCallback'] = (
appState,
) => {
router.navigate({
replace: true,
to: appState?.returnTo || window.location.pathname,
});
};
const onRedirectCallback: Auth0ProviderOptions['onRedirectCallback'] = (
appState,
) => {
router.navigate({
replace: true,
to: appState?.returnTo || window.location.pathname,
});
};
export const Route = createFileRoute('/_auth')({
beforeLoad: async ({ context, location }) => {
if (!context.auth.isAuthenticated) {
context.auth.loginWithRedirect({
appState: { returnTo: location.pathname },
});
}
},
});
export const Route = createFileRoute('/_auth')({
beforeLoad: async ({ context, location }) => {
if (!context.auth.isAuthenticated) {
context.auth.loginWithRedirect({
appState: { returnTo: location.pathname },
});
}
},
});
Does the router being used outside of the RouterProvider cause this to happen?
genetic-orange
genetic-orangeโ€ข13mo ago
maybe ... can you reproduce this in a minimal complete example by stubbing out those providers? if yes, please fork one of the existing router examples on stackblitz
flat-fuchsia
flat-fuchsiaOPโ€ข13mo ago
I could definitely get a stackblitz going but I'm not sure about the Auth0 integration with its secret keys
genetic-orange
genetic-orangeโ€ข13mo ago
that's why it would be good if you could mock this totally
flat-fuchsia
flat-fuchsiaOPโ€ข13mo ago
I'll see what I can whip up tonight Note: It goes back to the initial state passed in through createRouter until refreshing the page
genetic-orange
genetic-orangeโ€ข13mo ago
which router version are you on?
flat-fuchsia
flat-fuchsiaOPโ€ข13mo ago
When I first asked this question I was on 1.47.1 then I updated to 1.51.1 and tested again with the same results
genetic-orange
genetic-orangeโ€ข13mo ago
ok, just wanted to make sure
fascinating-indigo
fascinating-indigoโ€ข13mo ago
I have the same issue on latest
fascinating-indigo
fascinating-indigoโ€ข13mo ago
GitHub
Router's context doesn't have injected values on initial render fro...
Describe the bug import { Auth0ContextInterface, Auth0Provider, useAuth0, User } from '@auth0/auth0-react' import { createRouter, RouterProvider } from '@tanstack/react-router' impo...
fascinating-indigo
fascinating-indigoโ€ข13mo ago
Posted an issue here
genetic-orange
genetic-orangeโ€ข13mo ago
@jt can you please provide a minimal complete example?
fascinating-indigo
fascinating-indigoโ€ข13mo ago
https://add-ndmoby1i3-add-wrk.vercel.app/ - if you click dashboard here and signup with a random username/password, the redirect callback will show you the missing context
genetic-orange
genetic-orangeโ€ข13mo ago
no, I mean code
fascinating-indigo
fascinating-indigoโ€ข13mo ago
Oh, does the code in the github issue not suffice? I can answer questions or provide more if necessary
genetic-orange
genetic-orangeโ€ข13mo ago
it's not complete I cannot debug this
fascinating-indigo
fascinating-indigoโ€ข13mo ago
Hmm it's probably not possible without using an Auth0 setup no? Requires external architecture
genetic-orange
genetic-orangeโ€ข13mo ago
we typically use minimal examples based on the existing router examples such as https://tanstack.com/router/latest/docs/framework/react/examples/kitchen-sink-file-based
React TanStack Router Kitchen Sink File Based Example | TanStack Ro...
An example showing how to implement Kitchen Sink File Based in React using TanStack Router.
genetic-orange
genetic-orangeโ€ข13mo ago
can't auth0 be mocked away? it's "just" a react provider
fascinating-indigo
fascinating-indigoโ€ข13mo ago
Let me try to see if I mock if it reproduces the error I'll let you know
genetic-orange
genetic-orangeโ€ข13mo ago
I guess the problem is just this: two providers require each other in a circular dependant way
fascinating-indigo
fascinating-indigoโ€ข13mo ago
One thing I tried was router = useRef(router) in case the auth0 provider was getting a stale router, but the issue still came up
genetic-orange
genetic-orangeโ€ข13mo ago
the router instance should not be the problem <RouterProvider> updates the router context, but in your case it was not rendered yet so router does not have the auth prop in its context
fascinating-indigo
fascinating-indigoโ€ข13mo ago
Ah so maybe race condition
if (auth.isLoading) {
return null
}
if (auth.isLoading) {
return null
}
blocking the RouterProvider render on auth.isLoading() is fine mostly, except in this case of the auth0 redirect callback. Would it be helpful if I still worked on a code example for you?
genetic-orange
genetic-orangeโ€ข13mo ago
maybe not necessary, it seems like a fundamental circular dependency that needs to be broken up somehow
fascinating-indigo
fascinating-indigoโ€ข13mo ago
onRedirectCallback={(appState) => {
setTimeout(() => {
myRouter.navigate(appState?.navigateTo! || { to: '/' })
}, 1)
}}>
onRedirectCallback={(appState) => {
setTimeout(() => {
myRouter.navigate(appState?.navigateTo! || { to: '/' })
}, 1)
}}>
Not sure this is a great solution, but this seems to buffer the navigate after the rendering cycle for a temporary fix to the race condition
genetic-orange
genetic-orangeโ€ข13mo ago
I'm having the same issue. Trick is that it is partially occuring due to Auth0's loading time. Route context is fed before Auth0 is done loading. Auth0 is also using a hook so we are limited to the rules of hooks. I'm using Auth0 with pkce (all public keys) so I can try to get an example posted tomorrow
fascinating-indigo
fascinating-indigoโ€ข13mo ago
Yeah for now, you can try my solution. It will buffer the time = 0 render and I think it is fine after waiting 1 millisecond for the next render.
genetic-orange
genetic-orangeโ€ข13mo ago
Thanks!
flat-fuchsia
flat-fuchsiaOPโ€ข13mo ago
https://stackblitz.com/edit/tanstack-router-kcqjyt I could not reproduce the issue exactly. However, there is an await sleep(1) inside of the onFormSubmit function in routes/login.tsx:52 and without that sleep it does not redirect. It doesn't crash, but it also doesn't redirect. So @jt I don't think your "hack" is too offbase hahah In this stackblitz I altered the main.tsx to remove defaultPreload, auth.tsx to use the router.navigate inside of login, routes/_auth.tsx in order to use context.auth.login instead of throwing a redirect. It's the closest replication I could get but alas I could not replicate it
Brennen Davis
StackBlitz
Router Authenticated Routes Example (forked) - StackBlitz
Run official live example code for Router Authenticated Routes, created by Tanstack on StackBlitz
flat-fuchsia
flat-fuchsiaOPโ€ข13mo ago
https://youtu.be/eiC58R16hb8?si=SMOYge8Dd7Sdpty8&t=276 I think this video helps explain how setTimeout can be helping us here by delaying execution
Lydia Hallie
YouTube
JavaScript Visualized - Event Loop, Web APIs, (Micro)task Queue
Learn how the browser event loop, task queue, microtask queue, and Web APIs work together to enable non-blocking, asynchronous JavaScript. - https://www.patreon.com/LydiaHallie - https://buymeacoffee.com/lydiahallie - https://twitter.com/lydiahallie - https://www.linkedin.com/in/lydia-hallie/ - https://instagram.com/theavocoder Timestamps: 0:...
genetic-orange
genetic-orangeโ€ข13mo ago
This one helped for me. I am loading Auth0, passing the Auth0 auth object into context, then using that context in beforeLoad to redirect based on authentication status. This solution checks for isLoading and only returns a RouterProvider after the Auth0 library has loaded. https://github.com/TanStack/router/discussions/1322
GitHub
Auth0 Provider beforeLoad Implementation ยท TanStack router ยท Discus...
Hello. I am running into an issue where my beforeLoad function is running before my AuthProvider finishes loading. My Auth Provider hook provides the following values: isLoading, isAuthenticated, a...
flat-fuchsia
flat-fuchsiaOPโ€ข13mo ago
Also looks like this issue may be related https://github.com/TanStack/router/issues/1531
GitHub
Context is undefined after redirect ยท Issue #1531 ยท TanStack/router
Describe the bug I use authentication, similar to Authenticated Routes example from documentation. So, when user is on /$lang page, if user is not authorised, user is been redirecting to /$lang/log...
flat-fuchsia
flat-fuchsiaOPโ€ข13mo ago
I've tried this as well but the issue still persists for me because we call router.navigate in the onRedirectCallback it doesn't rely on RouterProvider existing. onRedirectCallback is called regardless of the auth.isLoading state.
function InnerApp() {
const auth = useAuth0();
if (auth.isLoading) return <div>Auth Loading...</div>;
return <RouterProvider router={router} context={{ auth }} />;
}

function App() {
return (
<Auth0ProviderWithNavigation
domain={...}
clientId={...}
authorizationParams={{
redirect_uri: '...',
audience: '...',
}}
>
<InnerApp />
</Auth0ProviderWithNavigation>
);
}
function InnerApp() {
const auth = useAuth0();
if (auth.isLoading) return <div>Auth Loading...</div>;
return <RouterProvider router={router} context={{ auth }} />;
}

function App() {
return (
<Auth0ProviderWithNavigation
domain={...}
clientId={...}
authorizationParams={{
redirect_uri: '...',
audience: '...',
}}
>
<InnerApp />
</Auth0ProviderWithNavigation>
);
}
type Auth0ProviderWithNavigationProps = PropsWithChildren<Auth0ProviderOptions>;

function Auth0ProviderWithNavigation({ children, ...props }: LEAuth0ProviderProps) {

// This is called as soon as Auth0 comes back from logging in.
// Because our Auth0Provider is rendered before our RouterProvider
// the RouterProvider.context isn't initialized yet as we attempt to navigate
// to an authenticated route and our `beforeLoad` fires probably milliseconds
// before the context is populated. The `setTimeout` puts our navigate function
// on the Task Queue allowing the Microtask queue to fully execute before navigating away
const onRedirectCallback: Auth0ProviderOptions['onRedirectCallback'] = (
appState,
) => {
// This `setTimeout` is necessary to delay navigation
// until after the RouterProvider has rendered with the auth context
setTimeout(() => {
router.navigate({
replace: true,
to: appState?.returnTo || window.location.pathname,
});
}, 1);
};

return (
<Auth0Provider {...props} onRedirectCallback={onRedirectCallback}>
{children}
</Auth0Provider>
);
}
type Auth0ProviderWithNavigationProps = PropsWithChildren<Auth0ProviderOptions>;

function Auth0ProviderWithNavigation({ children, ...props }: LEAuth0ProviderProps) {

// This is called as soon as Auth0 comes back from logging in.
// Because our Auth0Provider is rendered before our RouterProvider
// the RouterProvider.context isn't initialized yet as we attempt to navigate
// to an authenticated route and our `beforeLoad` fires probably milliseconds
// before the context is populated. The `setTimeout` puts our navigate function
// on the Task Queue allowing the Microtask queue to fully execute before navigating away
const onRedirectCallback: Auth0ProviderOptions['onRedirectCallback'] = (
appState,
) => {
// This `setTimeout` is necessary to delay navigation
// until after the RouterProvider has rendered with the auth context
setTimeout(() => {
router.navigate({
replace: true,
to: appState?.returnTo || window.location.pathname,
});
}, 1);
};

return (
<Auth0Provider {...props} onRedirectCallback={onRedirectCallback}>
{children}
</Auth0Provider>
);
}

Did you find this page helpful?