Google-api-javascript-client: working example with ux_mode = 'redirect'

Created on 15 Dec 2017  路  13Comments  路  Source: google/google-api-javascript-client

There are none working examples with ux_mode='redirect' in the documentation. This is very unclear how to make this work.
Please somebody post full working code with ux_mode='redirect'

Thank you

Most helpful comment

@TMSCH I will post now the problem I encountered with with ux_mode='redirect' and how I solved it:

Problem: examples that can be found here: https://developers.google.com/identity/sign-in/web/sign-in
all implement the pop-up option. They tell in one way or another to write a response function which is triggered automatically by google when the user completes the login in the popup window.

Now when you try to use ux_mode='redirect' first you realise that you must also use another parameter redirect_uri which is not needed when you implement popup. This is because you leave entirely your website to open google page with login and then Google needs to know where to redirect user after the login was successful. The redirect_uri parameter you pass must be also added in console.developers.google.com in the 'credentials' --> 'redirect uris' section.

After user is redirected back to your page after successful login (let's say to https://somedomain.com/googleresponse ) you need to have the javascript code there (the code that you would normally have in your response function when implementing popup option). This code also needs to trigger automatically.


You may ask why not just give the 'redirect_uri' parameter the same as the login page(where is google button) and just let the google trigger the response function after you come back from google.
The problem is the Google doesn't know if you came back from google or you just landed on the login page - the google triggers the response function if the user is logged in in the browser (detects cookies). If you let the google control the response function, when the user who was already logged in with google in their browser before even landing on your page (say was logged in gmail or youtube etc) opens your login page, it will cause the google to trigger the function right away. And the problem with that is, you might have some information on the login page you want the user to read first or you might have other options like log in with facebook etc and you want the user to first click the button: 'login with google' and not trigger the response automatically.

Solution:
When using ux_mode='redirect' pass some redirect_uri='https://mydomain.com/googleresponse and execute there the code form the response function on your own

Please let me know if anything is unclear above, I'll edit or clarify. I managed to solve my problem how I described above.

All 13 comments

This mode doesn't require any specific setup as the library handles everything under the hood. What problem are you encountering?

We are also trying to implement the signIn() functionality with ux_mode:"redirect". However, finding that after account selection and redirect, checking isSignedIn.get() returns false. Basically, it's as if the User never logged in.

Is this not working correctly? Or is it not the way to use the redirect mode?

To confirm, the redirect_uri returns the user to the same page that they were on originally. On redirect, there is an id_token parameter (along with a few others) in the URI. We can figure out how to use that for server side flows. But what about client side? Can we somehow use that parameter to plug it in to the gapi.auth2 flow, and "manually" sign in the User?

Thanks!

Actually, there's a correction to the above. Upon further testing, it seems that the first time the login is performed with credentials entered (ie. in an Incognito window) via the signIn() ux_mode:"redirect", the user gets logged in correctly and isSignedIn.get() returns true.

However, when calling getAuthInstance().signOut(), and performing the same flow again - signIn() ux_mode:"redirect", with the account_selector option, isSignedIn.get() returns false.

Interestingly enough, if I log into the app the first time in Incognito, then go to gmail.com and sign out of the Google account. When I return to the app, I'm signed out, and when I sign in again, it works!

But, if I log into the app with Incognito, call signOut(), then go to my gmail and log out. When I return to the app, and I sign in again, isSignedIn.get() returns false.

I thought maybe the issue is with the signOut() function, so I tried disconnect() with the same result. I guess they probably do the same thing.

Any ideas?

@alex-deaconu I can't reproduce the issue. Is there any log in the console? Any failed network requests? Are you always calling gapi.auth2.init` with the same options?

A snippet of code reproducing the issue would be helpful :)

@TMSCH I will post now the problem I encountered with with ux_mode='redirect' and how I solved it:

Problem: examples that can be found here: https://developers.google.com/identity/sign-in/web/sign-in
all implement the pop-up option. They tell in one way or another to write a response function which is triggered automatically by google when the user completes the login in the popup window.

Now when you try to use ux_mode='redirect' first you realise that you must also use another parameter redirect_uri which is not needed when you implement popup. This is because you leave entirely your website to open google page with login and then Google needs to know where to redirect user after the login was successful. The redirect_uri parameter you pass must be also added in console.developers.google.com in the 'credentials' --> 'redirect uris' section.

After user is redirected back to your page after successful login (let's say to https://somedomain.com/googleresponse ) you need to have the javascript code there (the code that you would normally have in your response function when implementing popup option). This code also needs to trigger automatically.


You may ask why not just give the 'redirect_uri' parameter the same as the login page(where is google button) and just let the google trigger the response function after you come back from google.
The problem is the Google doesn't know if you came back from google or you just landed on the login page - the google triggers the response function if the user is logged in in the browser (detects cookies). If you let the google control the response function, when the user who was already logged in with google in their browser before even landing on your page (say was logged in gmail or youtube etc) opens your login page, it will cause the google to trigger the function right away. And the problem with that is, you might have some information on the login page you want the user to read first or you might have other options like log in with facebook etc and you want the user to first click the button: 'login with google' and not trigger the response automatically.

Solution:
When using ux_mode='redirect' pass some redirect_uri='https://mydomain.com/googleresponse and execute there the code form the response function on your own

Please let me know if anything is unclear above, I'll edit or clarify. I managed to solve my problem how I described above.

@CodingToBeWithHer very informative. Actually, you're spot on as to why this was not working for us.

The cause of the issue seems to be very specific in our case. We're using Angular with HTML5 mode enabled (https://docs.angularjs.org/guide/$location#html5-mode). This automatically transforms hash (#) parameters to URL parameters, basically removes the hash.

Now, the response from the redirect page is something like http://localhost:8000/#id_token=efdJd... but html5 mode turns it into http://localhost:8000/id_token=efdJd... right away. I'm assuming that at load the gapi.auth2 library is supposed to read this id_token and use it somehow. But, since the token is no longer part of the hash parameters (instead it's part the path) it can no longer find it.

Actually, I tested without html5 mode and it works perfectly (I believe gapi actually cleans up the id_token parameter once it's done with it).

@TMSCH as requested, here's a simplified HTML page where I'm demoing this issue:
https://gist.github.com/alex-deaconu/544cdf6d37423b5f92ad72245958a97b
Make sure you add in your client id and have the proper URLs set up for it (Javascript origins & redirect URIs). Example uses Angular.

I guess the question is now if there's a specific way to resolve this without creating a separate page to handle this. Can we set up the gapi.auth2 library to return a querystring parameter rather than a hash? Something like http://localhost:8000/?id_token=efdJd... I think that would be a pretty logical solution for this. Any other ideas?

Thanks!

@CodingToBeWithHer thanks for the detailed investigation and for explaining how you fixed your issue. Your solution seems indeed the right one for your use case.

Now when you try to use ux_mode='redirect' first you realise that you must also use another parameter redirect_uri which is not needed when you implement popup.

This isn't correct. The redirect_uri parameter is optional, and if not specified, the current URL is used.

The problem is the Google doesn't know if you came back from google or you just landed on the login page - the google triggers the response function if the user is logged in in the browser (detects cookies). If you let the google control the response function, when the user who was already logged in with google in their browser before even landing on your page (say was logged in gmail or youtube etc) opens your login page, it will cause the google to trigger the function right away.

The behavior you mention is a crucial feature of the library. The user will be signed in automatically in two scenarios:

  1. The user has previously signed into your app using the Google Sign-In in the current browser.
  2. There is only 1 Google Account logged into the browser and it has already been approved for your app (i.e. the user already signed in to your app, potentially on a different device).

@alex-deaconu this is an interesting use case we hadn't thought about. There is currently no option to use querystring parameter instead of hash, so your workaround is the way to go.

@TMSCH sounds good.

I was able to fix the hash removal issue by applying the fix recommended here: https://github.com/angular/angular.js/issues/5529, to be more specific via $locationProvider.hashPrefix("/"); (https://docs.angularjs.org/guide/$location). This prevents the hash from being converted into a path. The fact that the gapi cleans up the hash parameter is helpful in this case.

Just as an aside, another feature we had to create a workaround for is the absence of a state parameter for the signIn function, with ux_mode='redirect'. Since the redirect_uri has to be fixed due to the Client Id restriction, we have to use browser storage to be able to restore the original state for the user. It would be nice if the API provided that similar to the one provided by the https://accounts.google.com/o/oauth2/auth endpoint.

@alex-deaconu the state parameter of the OAuth 2.0 Authorization endpoint is meant for security reason:

RECOMMENDED. An opaque value used by the client to maintain
state between the request and callback. The authorization
server includes this value when redirecting the user-agent back
to the client. The parameter SHOULD be used for preventing
cross-site request forgery as described in Section 10.12.

from the specs. gapi.auth2 library uses a different mechanism to achieve the same goal, hence we do not currently support it in the signIn call.

To persists user's state, it seems like a good idea to use browser storage instead.

I dont know if I should bump this one or make new one, but I am running into similar problem now. After using ux_mode = 'redirect' I am auto-redirected back to the page, which I guess means in the google login I am already logged in. So the url looks like mysite.com/#scope=openid%20email%20profile%20https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/plus.me%20https://www.googleapis.com/auth/userinfo.profile&id_token=xxxxxxxx&login_hint=yyyyyyyy&client_id=zzzzz - and at this point the same init code that was used for signing in is called:

gapi.load('auth2', function () {
                var auth2 = gapi.auth2.init({
                    ux_mode: 'redirect',
                    client_id: 'zzzzzzzz',
                    cookiepolicy: 'single_host_origin',
                    scope: 'profile email'
                }).then(function () {
                    console.log(gapi.auth2.getAuthInstance().isSignedIn.get());
                });

this cleares /#scope=.... part of the url and logs false to the console. Obviously I have received id_token, but the lib does not seem to be happy with it.

edit: if I do

            var match = location.href.match(/id_token=([^&]+)/);
            if (match) {
                // pass match[1] to our server login script

it seems to pass google verification with no problems.

so now, with this hack, the problem is js lib thinks the user is logged out, and we cannot log the user out after we log them in :S

I've bumped into a similar issue of parameters getting removed from the url after Google redirects back and found out that it was caused by Office sign-in script. If you have multiple sign-in services running on the same page make sure you test them in isolation when you hit a problem.

This is a bit old...but hopefully someone can still help me.

So I'm trying to use ux_mode = 'redirect' which works as expected but the issue I'm running into is with the id_token.

If I don't use redirect mode and get the user id_token from GoogleAuth.currentUser and send that to my server for validation...no issue I get back the user basic profile info in the payload...however, if I use redirect mode and get the id_token from the url to be validated on the server, then the payload only have the token info, no profile info aside from email address...

Is there something I'm missing when initialize GoogleAuth object and signing in the user?

Was this page helpful?
0 / 5 - 0 ratings