Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auth0 state parameter not always passed through #73

Closed
ForbesLindesay opened this issue Nov 22, 2018 · 7 comments
Closed

Auth0 state parameter not always passed through #73

ForbesLindesay opened this issue Nov 22, 2018 · 7 comments
Milestone

Comments

@ForbesLindesay
Copy link

If you set the state manually, e.g. passport.authenticate('auth0', {state: 'some_state}) and then log in twice in parallel, the first request will correctly get the state passed through. The subsequent requests will all get a new random looking string, something like: g6Fo2SBZNjR2SndFQkZGQVAxS0s2MFVfVUxXQVB0MkNSdjRia6N0aWTZMmdhRm8yU0JOTTNWUlJtZEZVVFpqYVdKdlZWSTNVRmg1TTBSSmNWUTBMVU5qY0dVMFVno2NpZNkgcURNVUtTWkk5M0JlaVpJQ1A2RVZ0bk9tMUt4YTJDZlM

The specific steps to reproduce are:

  1. Initiate login on one browser tab
  2. Initiate login on a second browser tab
  3. complete login on first browser tab, note that state was passed through
  4. complete login on second browser tab, note that state was not passed through
@joshcanhelp
Copy link
Contributor

The state parameter is not meant to work across multiple browser tabs.

@ForbesLindesay
Copy link
Author

I think you've mis-understood this. I don't want the state parameter to be shared between browser tabs, I want the state parameter not to be affected by multiple parallel requests.

@joshcanhelp
Copy link
Contributor

Sorry for the fast reply. We've been a bit behind on issue here so I wasn't sure if it was still relevant.

I haven't looked in-depth here but the reason this typically fails is that a second long request overwrites the state value from the first login request, causing a mismatch.

Can you help me understand what the use case is for what you are describing here? This doesn't sound like something that a user would typically do or something we need to investigate a fix for.

Thank you!

@ForbesLindesay
Copy link
Author

When you load a web page in iOS safari, sometimes it pre-emptively requests some of the URLs you've requested previously for the same page. This seems to happen especially when the app is listed as a standalone app. If both requests require authentication, they both redirect to auth0. When the requests complete authentication (often transparent due to SSO) one has the correct state parameter and the other does not.

Whether or not you think this is a common use case (for us it is critical, but maybe we're unique), it is a bug and suggests some pretty lax attention to detail in the code. The state parameter should not be global, it should belong to the individual request, and the fact that the two requests interact in this way makes me seriously doubt that the implementation is robust and secure.

@joshcanhelp
Copy link
Contributor

lax attention to detail in the code ... makes me seriously doubt that the implementation is robust and secure

I think we can work through your issue together without statements like this.

What I'm not understanding here is why you would need to initiate two parallel login requests for the same application in the same browser. The state is not "global," it's tied to your login request but the login request is tied to your browser. I'm not sure why allowing login to happen twice is critical but it's certainly possible that I'm missing something specific to your implementation.

It's worth it to note that state storage and checking is handled entirely within Passport.js. The only thing we do with state is ensure that it's used if not indicated otherwise.

If you can share the code for your complete login and callback routes (with anything sensitive redacted), I'd be happy to take a look and see if something jumps out at me. I think I have something mis-configured in my session handling as I'm not able to pass anything in with state without getting an error.

@ForbesLindesay
Copy link
Author

The point is that I'm not able to prevent the browser making two parallel requests to restricted resources, I either have to redirect both of them through the auth flow, or reject one of them as access denied.

The bug I'm reporting isn't specifically in passport, but I couldn't find anywhere else to report it to auth0. The problem is auth0's OAuth implementation that should always pass through the state parameter, which should be tied to the individual authentication flow, not the user's browser in general.

@joshcanhelp
Copy link
Contributor

Just to make sure we're on the same page, since I'm still not totally clear what we can do to address this.

When the strategy is instantiated, you can turn state on:

var strategy = new Auth0Strategy(
  {
    domain: process.env.AUTH0_DOMAIN,
    clientID: process.env.AUTH0_CLIENT_ID,
    clientSecret: process.env.AUTH0_CLIENT_SECRET,
    callbackURL: process.env.AUTH0_CALLBACK_URL,
    state: true
  },
  function (accessToken, refreshToken, extraParams, profile, done) {
    // ...
    return done(null, profile);
  }
);

... and the state will be generated, stored, passed, and validated automatically. If you leave state off, then it's turned on automatically in the strategy here.

With state on, the expected (and confirmed) behavior is:

  1. Start login in one tab
  2. Start login in another
  3. Complete login in the first tab
  4. Callback throws an error in the first tab (state was overridden in the second tab)
  5. Complete login in the second tab
  6. Callback throws an error in the second tab (state was deleted when checking in the first tab)

You can pass in false for state:

var strategy = new Auth0Strategy(
  {
    domain: process.env.AUTH0_DOMAIN,
    clientID: process.env.AUTH0_CLIENT_ID,
    clientSecret: process.env.AUTH0_CLIENT_SECRET,
    callbackURL: process.env.AUTH0_CALLBACK_URL,
    state: false
  },
  function (accessToken, refreshToken, extraParams, profile, done) {
    // ...
    return done(null, profile);
  }
);

... and steps 4 and 6 above will succeed, instead of throwing an error. This is all default Passport behavior, except for state defaulting to true if nothing is indicated.

As for a custom state ... I'm not able to get Passport to use that no matter what I do. Can you share the code you're using to set that?

Any way I look at it ... where and how the state is stored is managed by Passport so I'm still not clear how this is a bug or problem with our OAuth implementation. I absolutely could be wrong but, at this point, I'm not seeing it. I'm not even sure how you would be able to store and verify a state value for an auth process in a specific tab.

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

No branches or pull requests

2 participants