10

I am working on a function which allows users to sign in on my website with their Google account.

My code is based on the Google documentation (others signIn() options are in meta tags).

function login() {
  gapi.auth.signIn({'callback':
    function (authResult) {
      if (authResult['status']['signed_in']) {
        console.log('Okay');
      }else {
        console.log('Error');
      }
    }
  });
}

When I call login(), a Google pop up appears, I approve the terms of my application and everything works fine.

But the callback is called twice :

  • 1st case: If I never approved apps permissions then the callback will be call at the opening of the pop up AND when I will approve the permissions. So it will write "Error" and "Okay".
  • 2nd case: If I already approved the permissions, it will write "Okay" two times.

I added the option 'approvalprompt': 'force' to the signIn() function. The callback function is no longer called twice but it forces the user to approve the app's permissions, even if previously approved. So it's not user friendly.

Is there a friendly user way to approve the app's permissions one time without having two callback ?

Thank you.

sylvhama
  • 251
  • 1
  • 4
  • 13
  • possible duplicate of [Google Login Hitting Twice?](http://stackoverflow.com/questions/23020733/google-login-hitting-twice) – Ben Smith Oct 01 '14 at 10:59
  • Seems like this a common problem. There is an answer here: stackoverflow.com/questions/23020733/… – – Jon Adams Jun 27 '15 at 17:32

6 Answers6

8

I'm facing this same issue here, but I'm calling gapi.auth.signIn() via a button click handler. The callback is still called twice. One thing I noticed between the two authResult objects was that authResult.status.method is 'AUTO' in the first call (before the popup window appears) and is 'PROMPT' in the second call after the window is auto-dismissed due to previous authorisation.

The solution I'm exploring now is to ignore the AUTO instance and only process the PROMPT instance of the callback. Not sure how this will work once I revoke the permissions within Google due to the lack of details in the docs on the 'status' object.

Drew Taylor
  • 514
  • 4
  • 6
6

I am facing the same issue: signin callback called twice in case of user that already granted permission; the local variable approach (initializedGoogleCallback) isn't working for me because it call the callback one time only when the user already granted access, but didn't call it if the user is the new one. After a bit of research (i especially dig in site using the g+ auth) i noticed that all of them use the 'approvalprompt': 'force' and they have the already granted user to reapprove a "Offline Access" policy everytime. Even the google example i followed to setup my app (https://developers.google.com/+/web/signin/javascript-flow) even if it did not mention it, it uses the "force" parameter. For the moment it seems the only solution if you want to use the javascript flow (that mean if you need a personal style signin button)

GreenAsJade
  • 14,459
  • 11
  • 63
  • 98
popeating
  • 386
  • 1
  • 2
  • 16
4

Try to register first call in some local variable and then process it

This quick solution helps me:

function login() {
  var initializedGoogleCallback = false
  gapi.auth.signIn({
    'callback': function (authResult) {
       if (!initializedGoogleCallback) {
         // after first call other will be ignored
         initializedGoogleCallback = true;
         if (authResult['status']['signed_in']) {
           console.log('Okay');
         } else {
           console.log('Error');
         }
       }
    }
  });
}

also you can add following code before call gapi.auth.signIn

window.removeEventListener('load')
Panoptik
  • 1,094
  • 1
  • 16
  • 23
  • It's a good solution thank you! But I still don't know why we have to do that, it's like "cheating". Maybe it's a bug or we just don't understand the reason. – sylvhama Jan 21 '14 at 00:56
  • 1
    I think the reason is in google js library. But google not provide normal way to remove all previous events before new method call. Also there is no source js to fully understand the reason and API don't describe this too. – Panoptik Jan 21 '14 at 08:45
1

That is the intentional plan for page level config! It being present in the page causes the callback to fire when the Javascript is finished loading. What you should do is prepare for that in your code.

Don't show the sign in button until you have received a callback - if authResult['status']['signed_in'] == true, then treat the user as signed in (setup a session etc, whatever you would normally do). If it is false, then display the button.

function signinCallback(authResult) {
  if (authResult['status']['signed_in']) {
    document.getElementById('signinButton').setAttribute('style', 'display: none');
    // Do sign in stuff here!
  } else {
    document.getElementById('signinButton').setAttribute('style', 'display: block');
  }
}

I would avoid using approval prompt force if you can!

bluish
  • 26,356
  • 27
  • 122
  • 180
Ian Barber
  • 19,765
  • 3
  • 58
  • 58
  • How can hiding a button help if user has to click it to trigger callback? Hiding a button doesn't fix a double callback issue. – Karmalakas Jun 19 '14 at 11:22
  • For the javascript method, they don't have to click it to trigger a callback - it will trigger as soon as it is added to the DOM. If using the page level config it will trigger as soon as the page load completes. So the flow would be: page loads, callback fired, if not signed in display button. – Ian Barber Jun 19 '14 at 17:58
  • Maybe I don't understand something there - [JS workflow](http://developers.google.com/+/web/signin/javascript-flow). Quote: _You should only use user initiated events to trigger this method, such as clicks, so that pop-up blockers do not prevent the authorization dialog from opening._ Also in the example _onload_ just calls `render()`, which just adds eventListener to the button. Callback isn't called on loading the page. – Karmalakas Jun 20 '14 at 06:08
  • So the popup for auth should only be triggered by click, but the status callback (for if they have previously signed in) is triggered when the button is added to the dom if using gapi.signin.render or when platform/plusone.js finishes loading if using meta tag based config (as long as thr callback method is defined in a meta tag of course) – Ian Barber Jun 22 '14 at 16:45
  • But wasn't the question about [javascript-flow](https://developers.google.com/+/web/signin/javascript-flow)? You are talking about [adding a button with javascript](https://developers.google.com/+/web/signin/add-button-javascript), right? – Karmalakas Jun 26 '14 at 06:57
  • Sure, that is the way I recommend using the sign in. The example in the docs is a way of doing it, but if you're implementing a button its probably not the right way of doing it. Gapi.auth.signin has some practical uses (e.g. if your sign in button is in a canvas element or webgl) but for a regular sign in button, it doesn't give advantage. Additionally, you can use the button to trigger the callback flow even if your user-active auth flow uses the gapi.auth.signin method - they're not exclusive of each other! – Ian Barber Jul 07 '14 at 13:10
1

Like the Drew Taylor's answer, to avoid the double callback with the pure javascript sign in solution, you can check the user's session state:

if (authResult["status"]["method"] == "PROMPT") {...}

I think that the callback with the AUTO method is fired by the bottom welcome bar that appears on first login.

valse
  • 11
  • 2
0

finally i solved with a workaround; i don't know if this is the correct way to approach or i am just cheating but i do this way:

first of all some script in the page (i am using bootstrap + jquery)

function render() {
    //I am not using it but kept anyway

 }
var i;
// Function called from a onClick on a link or button (the 'sign in with g+' button)
function gp_login() {
    i=0;
    $('#alertbox').remove();
    var additionalParams = {
     'callback': signinCallback,
     /*'approvalprompt': 'force' finally removed*/
    };
    $('#gp-login').button('loading');
    gapi.auth.signIn(additionalParams); 
}

function signinCallback(authResult) { //my callback function
        var email='';
        var given_name='';
        if (authResult['status']['signed_in']) { //get some user info
            gapi.client.load('oauth2', 'v2', function() {
                gapi.client.oauth2.userinfo.get().execute(function(resp){
                    email = resp.email; //get user email
                    given_name = resp.given_name; //get user email
                    family_name=resp.family_name;
                    id=resp.id;
                    if (i<2) { //execute the doLogin just one time (my cheat)
                        doLogin(email,given_name,family_name,id); //dologin does my logic with an ajax call to signup/register user to my site
                    }
                    i=2;
                });
            });
        } else {
        // Update the app to reflect a signed out user
      }

}

this approch have the doLogin part called just one time, but the callback is called twice (gapi.client.oauth2.userinfo.get() this function is called twice); with a bit more tweaking with the if / var check i think is possible to call everything once. This way if the user already granted the auth, it will be automatically signed.

I notice that sometimes google have a popup layer on the bottom of layer showing a "welcome back message", but i didn't understand when it appears or if i have to call it manually

popeating
  • 386
  • 1
  • 2
  • 16
  • I'm dealing with the same issue and I ended up implementing a similar 'fix' but with a true/false variable. – Francisc0 Mar 27 '14 at 20:00