Skip to content
This repository was archived by the owner on Mar 3, 2022. It is now read-only.

Docs: show state param #301

Open
brockallen opened this issue Mar 30, 2017 · 13 comments
Open

Docs: show state param #301

brockallen opened this issue Mar 30, 2017 · 13 comments
Assignees

Comments

@brockallen
Copy link
Contributor

No description provided.

@brockallen brockallen self-assigned this Mar 30, 2017
@arwalokhandwala
Copy link

@brockallen can you provide description for this, I would like to contribute to this. Thanks :)

@brockallen
Copy link
Contributor Author

I wanted to document how to send state on the request and then how to access it on the callback. You could put some text into this issue and I can copy it over into the wiki.

@arwalokhandwala
Copy link

@brockallen Ok sounds good. I am also facing this one issue where the UserManager.prototype.storeUser() uses user.toStorageString() but the toStorageString() doesn't capture the state key, so I am not able to access state, I know this can be solved by passing state in toStorageString(), can I raise a PR for that as well?

@kzazarashvili
Copy link

@brockallen any updates on this? Can I find somewhere documentation explaining how to pass state on the request and get back whenever using UserManager and signinRedirectCallback?

@dejan9393
Copy link

dejan9393 commented Oct 12, 2020

I believe that currently it's not possible to change the state ID without some crazy hacks. If that's the case, then setting up a single redirect_uri for multiple domains using the same OAuth client (useful if you have many dynamic subdomains using the same OAuth client) is virtually impossible, as you cannot access the state data from a different origin (it's stored in the opening window, which is inaccessible due to CORS). Allowing a custom state id would help with this issue.

Please correct me if i'm wrong, I've had a good look but it seems that this is currently the case.

Found this issue asking for support for a similar idea, the only difference being in my case, I cannot whitelist domains as they're dynamically generated (i.e. Review Apps via GitLab) #432

@brockallen
Copy link
Contributor Author

brockallen commented Oct 12, 2020

The short answer is that when you call UserManager.signinRedirect({state:foo}) that state will be handed back to you on:

signinRedirectCallback(function(user){
    var theFoo = user.state.foo.
})

@dejan9393
Copy link

Right, but is that outside the context of the popup (for example)? I was under the impression that that callback will be fired by the opener, not inside the popup.

@brockallen
Copy link
Contributor Author

@dejan9393 I don't know what your popup issue is. The state should work for popups as well.

@arwalokhandwala The state values you pass at signin time are only available during the callback when completing the protocol response. In short, they're meant to help your callback function restore the state of your app once the user returns from logging in. It's not meant to last longer than that.

@dejan9393
Copy link

dejan9393 commented Oct 13, 2020

@brockallen I'm saying that the state value you pass into signInRedirect or signInPopup does not get sent to the OAuth server; it sends an ID generated via the random function in random.js.
If my popup is on another domain, then localStorage from the opener window isn't accessible, meaning that I can't extract data from my state as the server returns only the random ID, not the associated data. This makes the returned state value a useless opaque value in the context of the popup window.

@brockallen
Copy link
Contributor Author

brockallen commented Oct 13, 2020

I'm saying that the state value you pass into signInRedirect or signInPopup does not get sent to the OAuth server; it sends an ID generated via the random function in random.js.

Correct, because in the protocol the state param is not intended as a way for the client to send data to the token server. If you want to send data (beyond the protocol) to the token server then put a query string argument in the authorize request.

If my popup is on another domain, then localStorage from the opener window isn't accessible, meaning that I can't extract data from my state as the server returns only the random ID, not the associated data. This makes the returned state value a useless opaque value in the context of the popup window.

I'm not following this and i just tested the sample using the popup flow and it's working AFAICT. You can even see in the logs that the state is returned:

image

The state is a handle to data from the client's domain and when the protocol response is processed in the client then the state is passed back in to load the correlated data.

@dejan9393
Copy link

Thanks for the detailed response.

I think the fundamental difference between your test and my use-case is that I have a multi-tenant application, of which some of the URLs are dynamically generated. For this reason, I cannot add a redirect URL for all instances of the application, so my solution was to route them all through a single constant redirect URL, which then redirects back to the original application (from inside the popup), before then posting back to the opener window.
The idea was to use the state parameter to hold the redirect URL to redirect back to after talking to the token server.

Example

We have 3 domains:
https://shop-abc.domain.com
https://shop-xyz.domain.com
https://shop-constant.domain.com

shop-abc and shop-xyz have dynamically generated URLs, shop-constant is always the same.

  • User attempts to log in to shop-abc
  • Popup window opens, with state set to a JSON encoded string containing https://shop-abc.domain.com/oauth-callback and redirect_uri set to https://shop-constant.domain.com/oauth-callback
  • Server responds, returning response to shop-constant.domain.com/oauth-callback (at this step we'd usually close the popup and let the opener know the results from the token server, but CORS won't allow us in this case)
  • The oauth-callback script extracts the redirect_uri from the JSON encoded state parameter* and redirects back to shop-abc.domain.com/oauth-callback, passing along the hash value containing the retrieved access token (i.e. implicit flow) -- *this is the bit that's not possible, as we cannot access the state data (only the opaque id) value inside the popup

Have I got the right idea, or is there a better solution to this problem?

Also would like to genuinely thank you for this amazing library that's saved me probably hundreds of hours, and for your time on these tickets - I know you're very busy with other OS projects, too.

@vaalkor
Copy link

vaalkor commented Mar 9, 2021

Thanks for the detailed response.

I think the fundamental difference between your test and my use-case is that I have a multi-tenant application, of which some of the URLs are dynamically generated. For this reason, I cannot add a redirect URL for all instances of the application, so my solution was to route them all through a single constant redirect URL, which then redirects back to the original application (from inside the popup), before then posting back to the opener window.
The idea was to use the state parameter to hold the redirect URL to redirect back to after talking to the token server.

Example
Have I got the right idea, or is there a better solution to this problem?

Also would like to genuinely thank you for this amazing library that's saved me probably hundreds of hours, and for your time on these tickets - I know you're very busy with other OS projects, too.

Did you solve your problem? I have exactly the same issue and am trying to figure out how to solve it.

@dejan9393
Copy link

@vaalkor I went with the solution I proposed in the comment you're replying to, didn't end up finding a conclusive answer and it seemed reasonable enough to me.

Basically you have 1 main redirect URL that all of your apps go through:

const redirectUri = "https://some-common-site.com/oauth-popup";

Save the actual redirect URL of the app you're currently in to the state parameter:

const state = {
  redirectUri: `${window.location.origin}/oauth-popup`,
};

Set up your oidc-client with that redirectURI and state parameter and trigger the popup/iframe flow:

const oidc = new UserManager({
  redirect_uri: redirectUri,
  ...
});

await oidc.signinPopup({state});

In the popup/iframe handler code (/oauth-popup, the receiver), check if the decoded state parameter has a redirectUri:

// At this point, we should be at https://some-common-site.com/oauth-popup
// Retrieve state from window.location.hash, then decode:
function oauthRedirect() {
  state = JSON.parse(state);

  if (!state.redirectUri) {
    return;
  }

  const url = new URL(state.redirectUri);
  
  // Check if we're already on the page
  if (url.origin !== window.location.origin) {
    // If not, redirect to it, preserving the URL hash
    window.location.href = `${state.redirectUri}${location.hash}`;
  }
}

I pulled out bits and pieces of my code so the above is not exactly complete, but hopefully it gives you a general outline of the process. Hope that helps!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants