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.stringify
to obtain a JSON-encoded string. I call this “appStateJSON”. I useencodeURIComponent
to 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
Auth0ProviderWithHistory
as 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.search
for 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 incomingappStateJSON
and destructures the values I need intotransientState
. Since this do not change (ever!), I use theuseMemo
hook 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.