How to preserve client state after loginWithRedirect?

I’ve been thrashing at this for weeks now with no progress.

I have a client-side React SPA. It is responsible for signing in a user. It calls loginWithRedirect. All is fine. EXCEPT …

After a successful login, the client-side SPA has to itself redirect back to the app that called it. The Auth0 documentation is confusing about how to accomplish that.

The client-side React App passes a reference to itself (https://localhost:3103/loginPersona) as the value of redirectUri in the options.

When that redirectUri is called, it is called with no other data in the query string or body.

When I add a state parameter to the options, I see no evidence that Auth0 does anything with it at all.

According to one Auth0 document:

After the request is sent, the user is redirected back to the application by Auth0. The state value will be included in this redirect. Note that depending on the type of connection used, this value might be in the body of the request or in the query string.

When I set a breakpoint in the callback and examine the value of window.location, there is no
evidence that the value of state that I passed is present in the resulting callback at all.

I fear I am missing something truly basic here – I’ve been at this task for weeks and I’m making no progress. Is there SOME sort of community that can offer some guidance?

Is it really this hard to sign in users?

Hey there @tms !

Have you taken a look at the appState property?

Let us know if that works for your use case!

Hi there! Sadly, I missed this reply earlier – I thought I was subscribed to email notifications on this topic. I’ve checked my inbound emails (I save everything), and I’ve received no notifications about this topic even though the UI says that I’m “watching” it. My most recent notification email from ‘community.auth0.com’ is a notification for a different thread (for a reply that’s also from you). Perhaps there’s an issue with the forum itself?

The top of the document you cite has a prominent note that it has been deprecated. It offers a link to “React Authentication By Example” developer guide.

I’m already familiar with both versions of that document – I see minimal mention of the appState parameter, and I haven’t yet found it in the nest of the Auth0 API documentation.

I’ll investigate this further by adding a value for appState. I will appreciate a URL to an API document that describes this parameter. It isn’t obvious to me what the Auth0 code expects as its value – perhaps a URL-encoded query string?

I appreciate your attention. I find myself wondering if there’s some way I might be able to contribute to an effort to improve the Auth0 documentation. This seems like solid code – I’m just struggling mightily to get my use-case actually running smoothly. I suspect the needed behavior has been present all along – it’s just taking me weeks to find it.

From a breakpoint set in the ProviderWithHistory.onRedirectCallback method,
I can see that the appState value I added to the options in loginWithRedirect is available in the ProviderWithHistory.onRedirectCallback method (it’s passed as the argument of that method).

I still don’t understand how to make the value(s) of appState available to the application.

For example, after a successful login I see the following value for appState:

"continuation=https://localhost:3003/browser&someKey=someValue"

This is progress.

This value has two keys: continuation and someKey.

I would like these two keys to be passed as props to the children of my container. How do I accomplish this?

I have finally found an approach that seems to work. I’m describing it here to preserve the context of the original topic. I’m contemplating the creation of a new topic that describes the more general problem it solves (passing parameters in the redirect URL).

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:

  1. Assemble the parameters to be passed in a Javascript literal object.

    In this example, I pass principalID, isNewUser, localEmail, and localName. These capture application state that needs to be preserved across the redirect.

    const appState = {
      principalID: principalID,
      isNewUser: isNewUser,
      email: localEmail,
      name: localName,
    }
    
  2. Use JSON.stringify to obtain a JSON-encoded string. I call this “appStateJSON”. I use encodeURIComponent to sanitize this for use in a URL.

    const appStateJSON = encodeURIComponent(JSON.stringify(appState))
    
  3. 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:

  1. 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.

  2. 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. The transientFromAppState_ arrow function parses the incoming appStateJSON and destructures the values I need into transientState. Since this do not change (ever!), I use the useMemo 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’m happy to create a new topic with just this response if the community thinks it will be helpful.

I strongly encourage the Auth0 team to provide something along these lines in the technical documentation. It took me months to finally hack together something that works.