google / google-api-javascript-client

Google APIs Client Library for browser JavaScript, aka gapi.
Apache License 2.0
3.22k stars 1.06k forks source link

Can We Use Sign In With Google with Google API JavaScript Client? #803

Open washamdev opened 2 years ago

washamdev commented 2 years ago

We integrated this google-api-javascript-client on our site like this:

<script async defer src="https://apis.google.com/js/api.js" onload="this.onload=function(){};handleClientLoad();" onreadystatechange="if(this.readyState==='complete'){this.onload();}"></script>

<script>
    function handleClientLoad() {
        gapi.load('client:auth2', initClient);
    }

    function initClient() {
        gapi.client.init({
            'clientId': '[OUR-CLIENT-ID].apps.googleusercontent.com',
            'discoveryDocs': ['https://mybusinessaccountmanagement.googleapis.com/$discovery/rest?version=v1'],
            'scope': 'https://www.googleapis.com/auth/business.manage'
        })
        .then(function () {
            let GoogleAuth = gapi.auth2.getAuthInstance();
            let user = GoogleAuth.currentUser.get();
            let oauthToken = user.getAuthResponse().access_token;
            // Now we can make API calls with the user's access token.
        });
    }
</script>

The problem I'm having is that when we tried to get our app approved by Google, they told us we need a Google-branded login button. I went to the "Sign In with Google" documentation and was going to integrate their sign in button into the site, but I cannot find any information on how to make that sign in button integrate directly with the JavaScript API. Sign In with Google wants us to load this JS file on our site:

<script src="https://accounts.google.com/gsi/client" async defer></script>

Which is obviously different from the JS file we're loading for the API:

<script async defer src="https://apis.google.com/js/api.js" onload="this.onload=function(){};handleClientLoad();" onreadystatechange="if(this.readyState==='complete'){this.onload();}"></script>

What am I missing? Do these not play together at all? How did you accomplish getting a Google-branded login button on your site that then integrates with being able to make API calls for a user on your site?

brdaugherty commented 2 years ago

I'll share the quick answer: use GIS to sign-in users and get and use access tokens. Now for more detail...

There are currently two sign-in options available: the old (api.js) and the new (gsi/client).

The old sign-in methods and library is being deprecated in March 2023. You may use the old library provided you follow the branding guidelines... with the knowledge that you need to get a plan in place to migrate to the new sign-in button and library.

The old (api.js) and new (gsi/client) libraries are not compatible for a significant number of reasons, suffice it to say backwards compatibility was not an afterthought, nor overlooked. Given technical and privacy design choices there are breaking changes in features and functionality between the two libraries.

A key difference between the old and new library is that branding and button rendering (PNG, sizing, etc) are handled by Google through the new library. When using the new library it eliminates the need for you to pass brand verification during app registration.

Another, more significant difference is a design split between authentication and authorization. Most applications require sign-in only, and what has been said above applies to the sign-in button and process itself.

In your case, you'll be accessing additional scopes to manage business profiles. The new library has new objects and methods to manage user consent and obtaining per user access tokens. I'd recommend you start by looking at a comparison of OAuth 2.0 flows to help decide which flow you'll need.

If you're already deeply familiar with the workings of the old to be deprecated library I would recommend starting with the migration guide for user authorization.

What may or may not be clear is that there is a single new library accounts.google.com/gsi/client which replaces functionality found in multiple older JS libraries and api.js and client.js. The new gsi/client library aka Google Identity Services has been designed to continue interoperating with gapi.client. Apps which make extensive use of it's functionality are supported, if you've a really simple app with uncomplicated API calls, direct XHR is also possible, hopefully examples help illustrate these choices.

heatworld1 commented 2 years ago

I am trying to migrate as well but I'm trying to access if the user is logged in or even if there is an access_token given on other pages of the application without having the prompt pop up. Something like gapi.auth2.getAuthInstance().isSignedIn.get()

brdaugherty commented 2 years ago

Session state and listeners and isSignedIn are not included in GIS. Management of a user's sign-in status to a website is now fully the responsibility of the website. Typically, a cookie, HTTP header, or other means can be used to manage user sign-in state to your website. Why has this changed? User privacy. It may also be helpful to review GIS how it works for a high level view of user sign-in to a Google Account, the UX options for sign-in, ID token and how these steps are different from session state management for your website.

hth, session state and sign-in is a significant change imho, and making the web more private and safe for everyone won't be as simple as flipping a switch, or as easy as making functionally equivalent or backward compatible APIs and methods -- so I wanted to say thanks in advance for helping to work towards this goal.

heatworld1 commented 2 years ago

@brdaugherty How would one go about getting a token on one page using the js method and using that same token on another page?

brdaugherty commented 2 years ago

Typically, a One Tap HTML object or JS would be included on all pages on your site, but conditionally displayed using prompt. The test to prompt the user or not would depend upon a check of your site's signed-in status for the user. For example, if you've previously set and then find a session cookie for the user you'd know not to prompt again for the ID Token. This may help avoid users having to needlessly sign-in.

Note that ID Tokens do expire, the exp field shows when. Your site's user sessions may last a longer time than the ID Token expiration. It is really up to you if you'd want to check if the user-agent presenting the session cookie is able to sign-in and authenticate with their Google Account (via prompt and ID Token) or not. It may be useful if suspicious activity occurs on your site to use prompt() to challenge the user to sign-into their Google Account and confirm they are who they claim to be.

heatworld1 commented 2 years ago

I guess I'll give a little more information is needed. It's a multiple page site that will allow users to add events to their Google calendar using the Google API.

One page is the page to turn on the ability to do so which creates the access token.

Then on the other page they create tasks which are then posted to the Google calendar API using the access token. What's weird is that when i access the token from gapi to make the request it the expiry time didn't decrease. And when I check again it's as if it automatically asks for a new token automatically.

brdaugherty commented 2 years ago

gapi.auth2 would automatically refresh the access token, the expiry time behavior is the same for old/new libraries... it's just the request/response for a new access token used to be auto-magic and apps were unaware of what occurred within the old auth2 library.

With GIS you'll need to explicitly request a new access token when the prior token expires. If user prompting is undesirable, auth code flow from your backend platform with refresh token can be used to programmatically replace expired access tokens.

heatworld1 commented 2 years ago

Thank you looks like I might have to go the refresh token route so that I can silently verify the access code. It would be a bad experience if I continue with the front-end migration. Because I was thinking to get the access_token and then saving it to local storage and then on the pages that need it I would verify against google and if it expired get a new one and save it to local storage. But that would end up showing the pop-up now since gapi auth2 didn't.

brdaugherty commented 2 years ago

Yes, authorization code is the recommended approach -- offering the best security and user experience. There is one last option to decide on... using popup or redirect mode to send the auth code to the back-end (because who doesn't want more options). Popup is recommended as it has improved security and user experience when compared to using a browser redirect.

heatworld1 commented 2 years ago

Hmm why would one do the redirect method?

kis commented 1 year ago

@brdaugherty is there a possibility to still use old gapi.client approach to access Google API to get directory people (with authorization migrated to new google identity services) ? I see no examples of how to migrate calls to API's. If the js client library is being deprecated, what will replace it as a frontend solution? On documentation there's only Node.js google cloud library. Thanks!

brdaugherty commented 1 year ago

@kis Yes, gapi.client can still be used, and should be in anything but simple use cases.

gapi.client may not make sense if all that is needed is a handful of simple api calls; xhr or fetch work just fine. However, for more complex apps making a non-trivial number of api calls it is expected that gapi.client is used to make working with a series of chained dependent calls easier (a->b->c: method c needs output of method b which in turn needs output from method a). GAPI excels at automatically parsing discovery documents and making complex REST API surfaces easier to manage.

Here's a trivial example of the expected pattern of mixing gsi/client (for managing auth) and gapi.client (for navigating individual APIs).

There is no expectation that anyone would replace gapi.client.* calls to the generated api surfaces built from the discovery doc pulled in during gapi.client.load().

Due to how webpack and JS "modules" are released, I know the border between auth and client functionality is confusing, but conceptually only the access token, id token, and session handling methods are being deprecated from gapi.client, the remainder of functionality remains as-is and will continue to work as it does today.

The list of methods and objects being removed/replaced from gapi.client is here. If you don't see a gapi.client.some_method_name() listed, it'll continue to function.

Node.js libraries should be used for any back-end, platform work. It's expected that gapi.client and the new gsi/client are used only in browser, e.g. frontend solutions, single-page apps.

Hope this helps.