When a React application calls loginWithRedirect with default values for the options argument, the state of the application is cleared when the application is called back after the redirect.
The approach described here bundles the state to be recovered into a Javascript literal object and uses options.appState so that the inbound redirect contains the desired state.
This is all in the context of React front ends that rely on the useAuth0 hook.
I invite constructive feedback about issues this approach might create, especially around security. If sensitive information is being exchanged, then it might make sense to encrypt or hash the appStateJSON or the values it contains.
The general approach is:
Preparing loginWithRedirect:
-
Assemble the parameters to be passed in a Javascript literal object.
In this example, I pass
principalID,isNewUser,localEmail, andlocalName. These capture application state that needs to be preserved across the redirect.const appState = { principalID: principalID, isNewUser: isNewUser, email: localEmail, name: localName, } -
Use
JSON.stringifyto obtain a JSON-encoded string. I call this “appStateJSON”. I useencodeURIComponentto sanitize this for use in a URL.const appStateJSON = encodeURIComponent(JSON.stringify(appState)) -
In the “options” of the various redirect Auth0 calls, add a parameter binding with name “appState” and whose value is “appStateJSON” (formed above).
const options = { appState: {appStateJSON: appStateJSON}, redirectUri: redirectURI, login_hint: login_hint, prompt: 'login', } loginWithRedirect(options);
Handling the inbound redirect after login:
-
I modified the current recommended code for
Auth0ProviderWithHistoryas follows:const onRedirectCallback = (appState) => { if (appState) { const appStateJSON = appState.appStateJSON; const queryString = `?appStateJSON=${appStateJSON}` window.location.search = queryString; } history.push(appState?.returnTo || window.location.pathname); };This pushes appStateJSON onto the
window.location.searchfor use later. -
I add the following at or near the top of the React app that receives the redirect:
const queryParameters = new URLSearchParams(window.location.search); const appStateJSON = queryParameters.get("appStateJSON") const transientStateFrom_ = (anAppStateJSON) => { const answer = {} if (anAppStateJSON) { // Parse and handle incoming redirect state const redirectState = JSON.parse(anAppStateJSON); // Destructure redirectState answer.principalID = redirectState.principalID; answer.name = redirectState.name; answer.email = redirectState.email; answer.isNewUser = redirectState.isNewUser } return answer }; const transientState = useMemo( () => transientStateFrom_(appStateJSON), [ appStateJSON ] );I store the bindings from the redirect call in an JS object called
transientState. I have a convention of using a trailing underscore (“_”) to indicate that a parameter is expected. ThetransientFromAppState_arrow function parses the incomingappStateJSONand destructures the values I need intotransientState. Since this do not change (ever!), I use theuseMemohook to avoid thrashing on each render.I haven’t found a “destructuring” incantation (in Javascript) that works, so I’ve open coded it.
With this furniture in place, the React app can collect whatever it needs from transientState after a redirect.
In this example, ‘principalID’ is a React state variable created as follows:
const [principalID, setPrincipalID] = useState(null);
Here is a useEffect hook that reconstructs the principalID state on redirect:
useEffect(
() => {
if ( (principalID === null)
&& (transientState)
&& (transientState.principalID)) {
setPrincipalID(transientState.principalID)
}
},
[
principalID,
transientState,
setPrincipalID,
]
);
So far as I know, this approach is able to pass through arbitrary state (including embedded objects and such). I intentionally chose structures that do not require special magic for JSON.stringify and JSON.parse.
I’ve been using this code for a few weeks now and it seems to work fine.
Developers who are using Auth0 from React apps will surely need this mechanism.
I strongly encourage the Auth0 team to provide something along these lines in the technical documentation.