-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Client doesn't work if third-party session storage is disabled #260
Comments
Thanks for reporting this issue. The issue is already known to us, but I'm not sure when or whether a fix will be available. In the meantime, you may be able to work around this using the steps described in http://stackoverflow.com/questions/33200681/google-signin-not-working-in-safari-private-mode or something similar, although I have not tried that myself. |
@bsittler, I'm pretty sure that the workaround that you linked to does not help with the current issue. (The workaround suggests replacing the A different workaround to avoid the "Access is denied" exception was proposed on the Chromium issue tracker (https://bugs.chromium.org/p/chromium/issues/detail?id=357625). That workaround replaces localStorage and sessionStorage with proxy objects, using This second workaround did not work for me -- the exception is still thrown. Could this be because the function that causes the error is running in an iframe, which has a different window object from the main page? I'd appreciate any further suggestions :) |
Correct, this error occurs in an IFRAME on a separate origin, so a polyfill won't work. I was intending to suggest that you could test for working storage and avoid loading the library if the storage does not work. |
Ah, gotcha, thanks for the quick response. |
@mbloch @asadovsky we released a change that allows you to gracefully degrade under such environment, please have a look at the documentation: https://developers.google.com/identity/sign-in/web/reference#googleauththenoninit-onerror. |
@TMSCH do you mean gracefully degrade as in show an error message telling them they need to enable 3rd party cookies? |
@mjgallag yes, as you are now able to detect the issue, you can tell them that their browser is unsupported (and list potential reasons). It's very difficult to accurately detect that 3rd party data is blocked from our library, which is why we haven't yet been able to release a proper fix. |
@TMSCH got it, thanks for the quick reply! |
@mjgallag you're welcome! |
We are experiencing this issue as well. Using the idpiframe_initialization_failed error is really helpful for us to be able to message to our users what is happening. But even if we handle the error (
What do you guys think of updating the library code so that it doesn't actually throw the error, but only provide the error through the |
Hi @joeldenning! |
Would like to report we get same behaviour as @TMSCH. We cannot catch the error. Can't implement client-side OAuth2 when user doesn't allow 3rd party cookies/storage. |
Hi @philovivero ! We recently pushed a changed to not throw the error from within the IFrame. Could you test again and let me know if that works? You should not observe any JS error, but be able to catch it in the promise. Thanks! |
I'm not sure I will be able to repro. I ripped out all that code. For now we've decided to just let customers who uncheck that get failures, and when we look into it again, will likely implement OAuth2 server-side, since we assume at least some percentage of customers will be privacy-minded. |
@TMSCH I just checked today and the error still is getting thrown when the "Block third party cookies and site data" checkbox is checked in Chrome. Here are some screenshots: One thing to note is that I am using ravenjs (the js client for Sentry), which patches Was the change you pushed out recently something that would prevent this from happening? It looks like the iframe is communicating the error up to the main document through a port1 onmessage handler, and then the main document is still throwing the error. |
@joeldenning are you also catching the error in the initialization promise? If you don't, it will get thrown again (that's the standard behavior of a promise). I tried not catching it and observed the same behavior than you.
|
@TMSCH Thanks, I got it work! Since gapi promises aren't real promises and don't follow the spec, the code sample you provided didn't work (It seems like you can't call
Thanks for your help with this and for pushing out the fix. |
Oh yes, @joeldenning, you're right... Sorry about that. The return value is just a |
It would be helpful if this was covered in the docs as a known issue and potential caveat. We recently ran into this on a project during final testing, then had to scramble a server-side flow instead. (This seems like the only workaround, other than prompting users to change security settings in their browser?) Would've been useful to know this upfront - perhaps the could be some kind of introduction that explains pros and cons of different flows? |
@bkoski we will add this to the documentation, you're right, it should be mentioned. Server-side flow is indeed the most widely supported mechanism for now, as it doesn't rely on any storage or other characteristics of the browser. Keep in mind that such option in Chrome, block third party data, is not the default and a pretty advanced feature that most users won't have enabled. |
@TMSCH Privacy-conscious users enable it, and privacy-conscious tech geeks who set up systems for their less-technically-abled family and friends may also enable it. I never specifically enabled it on my system. I suspect one of the various privacy plugins I installed toggled it for me. I suspect there are many ways it becomes enabled where a non-advanced user will be the one suffering the consequence. @bkoski I also do not know any other work-around other than implementing it server-side. I actually think it should be strongly recommended to never do client-side auth until the situation with Chrome (and other browsers?) changes. |
@philovivero that's true. However, in recent browsers, the "Incognito", "InPrivate" or "Private" modes tend to have supplanted such option. It used to be very prominent in the browser's settings (I remember specifically IE), when now, it is relegated to a subcategory of advanced settings (eg Chrome), if not completely removed (I can't find the option in my version of Firefox).To be noted: in these modes and across browsers, |
So, for what it's worth, here is a quick implementation of a pure-client-side, pure-OAuth2 flow (that works with 3rd-party cookies disabled). If it serves to inspire this library's developers, wonderful; otherwise I can try to rigorize this and provide third-party support. index.html: <!DOCTYPE html>
<script src="https://apis.google.com/js/api.js"></script>
<script src="auth.js"></script>
<script>
const CLIENT_ID = '...';
const API_KEY = '...';
const SCOPES = ['https://www.googleapis.com/auth/calendar'];
googleAuthInit(CLIENT_ID, SCOPES).then(token => gapi.load('client:auth', () => gapiLoaded(token)));
function gapiLoaded(access_token) {
gapi.auth.setToken({access_token});
gapi.client.init({
apiKey: API_KEY,
discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest'],
}).then(() => {
gapi.client.calendar.events.list({
'calendarId': 'primary',
'timeMin': '2018-04-24T00:00:00Z',
'timeMax': '2018-04-26T00:00:00Z'}).then(response => {for (let event of response.result.items) console.log(event)});
});
}
</script> auth.js: function googleAuthInit(client_id, scopes) {
if (document.location.hash) {
let params = {};
for (let piece of document.location.hash.substr(1).split('&')) {
let [k, v] = piece.split('=', 2);
params[decodeURIComponent(k)] = v ? decodeURIComponent(v.replace(/[+]/g, ' ')) : '';
}
if (params.access_token && params.expires_in) {
localStorage.setItem('access_token_expires', Date.now() + Number(params.expires_in) * 1000);
localStorage.setItem('access_token', params.access_token);
let base = window.location.href;
let i = base.indexOf('#');
if (i > -1)
base = base.substring(0, i);
window.history.replaceState(null, document.title, base + (params.state ? atob(params.state) : ''));
}
}
function getAuthUri() {
let base = window.location.href, state = '';
let i = base.indexOf('#');
if (i > -1) {
state = base.substring(i);
base = base.substring(0, i);
}
return 'https://accounts.google.com/o/oauth2/v2/auth' +
'?client_id=' + encodeURIComponent(client_id) +
'&redirect_uri=' + encodeURIComponent(base) +
'&state=' + encodeURIComponent(btoa(state)) +
'&response_type=' + encodeURIComponent('token') +
'&scope=' + encodeURIComponent(scopes.join(' ')) +
'&include_granted_scopes=' + encodeURIComponent('true');
}
let access_token_expires_in = Number(localStorage.getItem('access_token_expires')) - Date.now() - 1000;
if (access_token_expires_in < 0) {
window.location.replace(getAuthUri());
return Promise.reject();
}
window.setTimeout(() => window.location.replace(getAuthUri()), access_token_expires_in);
return Promise.resolve(localStorage.getItem('access_token'));
} |
Here's a drop-in version of auth.js that should work without changes to the [current] examples. It first tries upstream gapi.auth2.init, but catches idpiframe_initialization_failed and tries conventional OAuth2, then shoves the access token back in via gapi.auth.setToken and forces gapi.auth2.getAuthInstance().isSignedIn.get() to return true. I'd love for upstream gapi.auth2.init to do something similar (is the uncompiled code for at least the auth2 component available?), but if that's not possible I can keep working on this, just let me know! (function() {
var patchedScriptOnload = false;
var patchedGapiLoad = false;
var patchedGapiAuth2Init = false;
tryPatchGapi();
function tryPatchGapi() {
if (!window.gapi) {
if (!patchedScriptOnload) {
for (let script of document.getElementsByTagName('script')) {
if (script.src.match(/^https:[/][/]apis[.]google[.]com[/]js[/]/)) {
patchedScriptOnload = true;
if (script.onload) {
console.log('gapi-issue260: Installing onload patcher to', script);
let scriptOnload = script.onload;
script.onload = function(e) {
tryPatchGapi();
scriptOnload(e);
};
} else {
console.log('gapi-issue260: Installing "load" event patcher to', script);
script.addEventListener('load', tryPatchGapi);
}
}
}
}
return;
}
if (!gapi.auth2) {
if (!patchedGapiLoad) {
patchedGapiLoad = true;
console.log('gapi-issue260: Patching gapi.load.');
let gapiLoad = gapi.load;
gapi.load = function(targets, callback) {
console.log('gapi-issue260: Watching gapi.load(', targets, ', ', callback, ').');
if (callback instanceof Function) {
gapiLoad(targets, () => {
tryPatchGapi();
callback();
});
} else {
var newCallback = {
callback: () => {
tryPatchGapi();
callback.callback();
},
onerror: callback.onerror,
};
gapiLoad(targets, newCallback);
}
};
}
return;
}
if (patchedGapiAuth2Init)
return;
patchedGapiAuth2Init = true;
console.log('gapi-issue260: Patching gapi.auth2.init.');
let gapiAuth2Init = gapi.auth2.init;
gapi.auth2.init = function(params) {
console.log('gapi-issue260: Watching gapi.auth2.init(', params, ').');
return new Promise((resolve, reject) => {
gapiAuth2Init(params).then(
resolve,
e => {
if (e.error == 'idpiframe_initialization_failed') {
console.log('gapi-issue260: Caught', e, '-- trying workaround.');
workaround(params).then(access_token => {
console.log('gapi-issue260: Success! Switching to gapi.auth and continuing.');
gapi.auth.setToken({access_token});
gapi.auth2.getAuthInstance().isSignedIn.get = () => true;
resolve();
});
} else {
reject(e);
}
});
});
};
}
function workaround(params) {
if (document.location.hash) {
let params = {};
for (let piece of document.location.hash.substr(1).split('&')) {
let [k, v] = piece.split('=', 2);
params[decodeURIComponent(k)] = v ? decodeURIComponent(v.replace(/[+]/g, ' ')) : '';
}
if (params.access_token && params.expires_in) {
localStorage.setItem('access_token_expires', Date.now() + Number(params.expires_in) * 1000);
localStorage.setItem('access_token', params.access_token);
let base = window.location.href;
let i = base.indexOf('#');
if (i > -1)
base = base.substring(0, i);
window.history.replaceState(null, document.title, base + (params.state ? atob(params.state) : ''));
}
}
function getAuthUri() {
let base = window.location.href;
let state = '';
let i = base.indexOf('#');
if (i > -1) {
state = base.substring(i);
base = base.substring(0, i);
}
return 'https://accounts.google.com/o/oauth2/v2/auth' +
'?client_id=' + encodeURIComponent(params.client_id) +
'&redirect_uri=' + encodeURIComponent(base) +
'&state=' + encodeURIComponent(btoa(state)) +
'&response_type=token' +
'&scope=' + encodeURIComponent(params.scope) +
'&include_granted_scopes=true';
}
let access_token_expires_in = Number(localStorage.getItem('access_token_expires')) - Date.now() - 1000;
if (access_token_expires_in < 0) {
window.location.replace(getAuthUri());
return Promise.reject();
}
window.setTimeout(() => window.location.replace(getAuthUri()), access_token_expires_in);
return Promise.resolve(localStorage.getItem('access_token'));
}
})(); |
Can Anyone help me with this Issue? I have to find out the issue that why website does not load the page properly after sign in with google api but when i run the project it works fine on my localhost |
@UserDac Hi, can you be more specific regarding your issue? Can you describe what happens? |
We have 587 users that experienced this problem, it would be nice to fix this in the library 🛠. |
@TMSCH Any news on progress made in the last year? |
Not a fix, but at least it won't fail silently. I have found that a message is posted to the parent window that indicates that cookies are not enabled when the page is first loaded. Only tested in Chrome with Third Party cookies turned off.
This is basic code and assumes if here is an error, that the error is a cookie error which I'm sure is not always the case, and I'm no JS expert... So modify as needed.
|
@TMSCH please can you answer me on the same issue https://stackoverflow.com/questions/58166870/google-api-sign-in-status-is-changed-after-refresh |
Any news or progress related to this issue? Is there at least a confirmed workaround until the issue is not completely fixed? Thanks. |
I pasted a workaround with some old js code earlier (not sure if it still works): My solution was to move everything to the backend: No issues with it so far. |
See my comment here. May help in your case |
This thread is a bit old so wanted to comment with what appears to be the current solution. Google's documents this as a Known Issue. Asking users to change their browser security settings is out of the question, so the only real solution is to abandon this JS library and implement server-side Auth 2.0 flow. Suggestion: As more browsers move toward disabling 3rd party cookies by default, this client side auth library is going to work for fewer and fewer users. This known issue about 3rd party cookies should be added as a big red warning at the beginning of the installation instructions. |
Google Chrome incognito blocks 3rd party cookies by default now |
Thanks Google for making me waste half my day on this unnecessary work. Really appreciate it. google/google-api-javascript-client#260 #21
Any updates on this? |
It seems like the Firebase team has a workaround: firebase/firebase-js-sdk#1040 |
Hi everyone! We might have found a very simple workaround: pwa-builder/PWABuilder#3286 (comment) (confirmed from multiple websites). |
In Chrome (55.0.2883.95), for privacy reasons, I set "block third-party cookies and site data".
With that, the code below produces this error:
Code:
This can be fixed by adding accounts.google.com to the exceptions list, or by allowing third-party storage wholesale, but really, this shouldn't be necessary; the client should degrade gracefully in the case where the browser doesn't allow third-party access to local storage.
The text was updated successfully, but these errors were encountered: