globus / globus-sdk-javascript

Globus SDK for JavaScript
https://globus.github.io/globus-sdk-javascript/
Apache License 2.0
6 stars 2 forks source link

Example on using state parameter to preserve query string #318

Open rpwagner opened 1 month ago

rpwagner commented 1 month ago

What problem are you trying to solve?

When a page receives a set of query parameters like https://example.org/plot.html?file=a.csv I would like to know the appropriate way to persevere the query string file=a.csv after a login redirect. I believe this is typically handled via the state parameter in the OAuth flow, though I could be mistaken.

We are using this model to have a single page that view and interact with data from Globus Collections that require access tokens. This way, way can have a single viewer page and load different files based on the query parameters. Examples of doing this for public data are in our Cheap and FAIR template repo (see the view.md and chart.md pages).

Describe the solution you'd like

It would great to have an example like the basic login page that shows how to use the state parameter or other means to do this.

Describe alternatives you've considered

As a workaround, I will try collecting the parameters prior to creating the login manager and calling manager.handleCodeRedirect() or login().

jbottigliero commented 1 month ago

@rpwagner – AuthorizationManager.login() and AuthorizationManager.handleCodeRedirect() will accept an optional options parameter. To pass query parameters through the OAuth handshake you can specify additionalParams.

manager.login({ additionalParams: { state: '...'  }});

But, taking a look a the internals here, I think you will encounter an Invalid State error when you call handleCodeRedirect – I can get that fixed.[^1]

However, the state should be used to prevent cross-site request forgery (see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1). If you don't provide a value, the SDK is creating a cryptographically strong random value that meets the OAuth spec requirements, if you provide your own you'll want to make sure you're doing some additional encoding rather than just supplying something like a.csv.

I can tell you in the applications we've been using the AuthorizationManager in, we've been managing application state outside of the OAuth handshake parameters (e.g. localStorage/sessionStorage values we reinstantiate post-handshake).

To do this, where you call manager.handleCodeRedirect you'll likely want to pass { shouldReplace: false }, process your storage then redirect on your own (e.g. window.location.replace).

[^1]: EDIT: This is addressed in #319 and will be available in the next patch release.

rpwagner commented 1 month ago

Thanks @jbottigliero. Session storage was what I also considered.

Is this roughly what you're describing?

      UI.SIGN_IN.addEventListener('click', () => {
          /**
           * This will redirect the user to the Globus Auth login page.
           */
          let queryString = window.location.search;
          sessionStorage.setItem("queryString", queryString);
          manager.login();
          queryString = sessionStorage.getItem("queryString");
          window.location.search = queryString;
      });
jbottigliero commented 1 month ago

Is this roughly what you're describing?

Rather than reading the value after .login() you would read that when the user is authorized (after the OAuth handshake).

Extending that basic example, I think you could do something like...

UI.SIGN_IN.addEventListener("click", () => {
  /**
   * This will redirect the user to the Globus Auth login page.
   */
  let queryString = window.location.search;
  // You'll set the value before you prompt for login.
  sessionStorage.setItem("queryString", queryString);
  // Since this will initiate the OAuth redirect, you can't really do any processing afterward.
  manager.login(); 
});

// ...

if (manager.authenticated) {
  const queryString = sessionStorage.getItem("queryString");
  if (queryString) {
     sessionStorage.removeItem("queryString");
     // do something;
  }