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

Oauth sign-in fails in Chrome when third party cookies blocked #72

Closed
camallen opened this issue Nov 17, 2017 · 22 comments · Fixed by #75
Closed

Oauth sign-in fails in Chrome when third party cookies blocked #72

camallen opened this issue Nov 17, 2017 · 22 comments · Fixed by #75

Comments

@camallen
Copy link
Contributor

camallen commented Nov 17, 2017

Linked to #30

The oauth flow works and the access_token is returned in the URL fragment however that redirect response tries to set a cookie as well. I assume this cookie set failure causes the code / a promise to fail even though we have all the information we need to authenticate the user.
Steps to recreate:

  1. Activate block third party cookies in chrome
  2. try to login to shakespearesworld.org and check the response from panoptes signin oauth flow.

Happy to help debug this as well

@eatyourgreens
Copy link
Contributor

I see this error in the console too, when logging in to antislaverymanuscripts.org with 3rd party cookies disabled:
Refused to display 'https://panoptes.zooniverse.org/users/sign_in' in a frame because it set 'X-Frame-Options' to 'sameorigin'.

Does that iframe try to set a cookie on the zooniverse.org domain?

@camallen
Copy link
Contributor Author

That is due to the lack of a session on the API, once you authenticate via the redirect process the iframe should launch fine for that domain.

@eatyourgreens
Copy link
Contributor

This error shows on the reload, after I've signed in at panoptes.zooniverse.org.

@camallen
Copy link
Contributor Author

OOI - which browser? With 3rd party tracking enabled, Firefox 58 works ok for me without a whitelist for that domain, chrome 63 requires me to whitelist that domain else the cookie doesn't get sent and the iframe is rejected without a session.

@eatyourgreens
Copy link
Contributor

Latest Chrome on OSX El Cap, with 3rd party cookies disabled in browser privacy settings.

I've traced the error as far as this section of code, when 3rd party cookies are disabled.

if (error.name === 'SecurityError') {
error = new Error('No existing Panoptes session found');
}

I think that cookie setting causes a Security Error to be raised by the iframe, which then blocks the auth flow. I'm not 100% sure why there's an iframe loading from panoptes.zooniverse.org. Is it there to refresh tokens for long sessions? I guess that would be blocked by browser security rules anyway.

@eatyourgreens
Copy link
Contributor

The cookie is actually set while the browser is still on panoptes.zooniverse.org, as far as I can tell from following the HTTP requests and responses, but of course it can't be read by an iframe if 3rd party cookies are blocked by antislaverymanuscripts.org.

@eatyourgreens
Copy link
Contributor

WTF is this line of code doing? I think it may be the cause of the error.

var newUrl = this._iframe.contentWindow.location.href;

@camallen
Copy link
Contributor Author

I'm not 100% sure why there's an iframe loading from panoptes.zooniverse.org. Is it there to refresh tokens for long sessions?

The initial login flow is via page redirects, and the token is provided in the url fragment on redirect back from panoptes / login server. The iFrame is to refresh the token without redirecting users in the middle of a task and thus losing data, unexpected page behaviour from sites, etc. This was the first way Roger got it working for refreshing, load the iFrame in the background to get the token without any page redirection.

The cookie is actually set while the browser is still on panoptes.zooniverse.org

Yep

but of course it can't be read by an iframe if 3rd party cookies are blocked by antislaverymanuscripts.org

This will hinder the ability to refresh the token (what the iframe is for), but not the inital redirection flow that does return the access code (look closely at the URL you should see the fragment on initial redirection before the JS strips it out. The issue is that the iframe code is stopping login full stop, even though it should be ok. Borked token refreshes are another issue and something we will have to look at (ouroboros does a funky scheme for this with an api proxy).

@camallen
Copy link
Contributor Author

Re #72 (comment) is that line just a fancy way to get the URL from the create iFrame context to the onload event?

@eatyourgreens
Copy link
Contributor

This will hinder the ability to refresh the token (what the iframe is for), but not the inital redirection flow that does return the access code (look closely at the URL you should see the fragment on initial redirection before the JS strips it out.

I don't think this is what the code is actually doing, if my reading of this function is correct. It seems to be getting your session from a temporary iframe, which is then destroyed.

_checkForPanoptesSession: function() {
var redirectUri = ls.get(LOCAL_STORAGE_PREFIX + 'redirectUri');
this.update({
_currentSessionCheckPromise: new Promise(function(resolve, reject) {
if (!redirectUri) {
reject(Error('No redirect URI found'));
}
// Create a new iFrame
var url = this._createOAuthUrl(redirectUri);
this._iframe = createIFrame(url);
// Try and get the token details from our iFrame. If it throws an error,
// it's because we're being redirected to the signin page (and therefore
// there is no session) - so we should also replace the security error
// with a more relevant one.
this._iframe.onload = function() {
try {
var newUrl = this._iframe.contentWindow.location.href;
if (checkUrlForToken(newUrl)) {
console.info('Found existing Panoptes session');
var newTokenDetails = parseUrl(newUrl);
resolve(newTokenDetails);
} else {
throw new TypeError('Valid OAuth details not found in URL');
}
} catch (error) {
if (error.name === 'SecurityError') {
error = new Error('No existing Panoptes session found');
}
reject(error);
} finally {
this._iframe = destroyIFrame(this._iframe);
}
}.bind(this);
}.bind(this))
});

@eatyourgreens
Copy link
Contributor

Should this session check be in an else block. At the moment it always runs, even if the preceding if did find a token in the page URL.

// If not, let's try and pick up an existing Panoptes session anyway
this._checkForPanoptesSession()
.then(function(tokenDetails) {
this._handleBearerToken(tokenDetails);
resolve(tokenDetails)
}.bind(this))
.catch(function (error) {
// We probably haven't signed in before
console.info(error);
resolve(null);
});

@eatyourgreens
Copy link
Contributor

The comment says 'If not…' but there's no conditional around that block. 🤔

@camallen
Copy link
Contributor Author

Re #72 (comment)

Itinitally the sign in button will do wire up a full redirect with the panoptes oauth URL, clicking sign in takes me to this URL.

location.assign(this._createOAuthUrl(redirectUri));

That iframe runs but it shouldn't fire until the user has a session on the oauth server. I assume based on the console warnings it does fire on page load...be nice to not use this iframe for anything but refreshing the token by converting my panoptes session (with cookie) to an oauth token.

@camallen
Copy link
Contributor Author

#72 (comment)

Probably - there are two distinct behaviours here:

  1. Login via full page redirect ala normal implicit grant flows.
  2. refresh the token somehow using my panoptes session (iframe was the initial way we did it).

@eatyourgreens
Copy link
Contributor

I missed that location.assign(). It will reload the browser tab, resetting the javascript state and losing the token from the URL. I could be wrong but I don't see any obvious code here that saves the token across page refreshes.

@eatyourgreens
Copy link
Contributor

@camallen
Copy link
Contributor Author

camallen commented Jan 26, 2018

Yeah - the point of that initial assigns is to wire up the signin button to redirect. After that it won't set the assign as the token exists..

@eatyourgreens
Copy link
Contributor

But there's no token after the redirect, and iframes are blocked so that method fails to get the session. This is where the code is failing. I think it should be using pushState rather than completely reloading the page (and the javascript) from scratch.

@camallen
Copy link
Contributor Author

The token does exist after a redirect back from the oauth provider.... it's in the url fragment.

@eatyourgreens
Copy link
Contributor

I meant after location.assign() redirects to a new page. That call completely reloads the javascript.

An alternative solution would be to write state to session storage before the assign, then read it out again afterwards.

@eatyourgreens
Copy link
Contributor

You can debug this in the Chrome network panel by checking the box to preserve logs across page refreshes.

@eatyourgreens
Copy link
Contributor

eatyourgreens commented Jan 26, 2018

Sorry, I should have linked to the code. This is the redirect that breaks login, not the one you linked above. I think we've been talking about two different bits of code.

// And redirect to the desired page
var url = ls.get(LOCAL_STORAGE_PREFIX + 'redirectUri');
location.assign(url);

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

Successfully merging a pull request may close this issue.

2 participants