Search code examples
google-apigoogle-api-js-clientgoogle-identitygoogle-api-javascript-client

How to use the Google API client for JavaScript with a One-Tap sign in flow?


I'm using Google One-Tap sign in to authenticate users, and after the user is authenticated I get an access token. I know that I can use this access token in order to work with the Google API client for JavaScript ("GAPI"). But I can't find any way to work with GAPI using this access token.

Is there any way to use GAPI assuming I already have an logged in user?

What I'm trying to do is access the user calendar after simply authenticating with One-Tap authentication and giving consent for the calendar once.


Solution

  • First of all:
    There is no way to "authenticate" the google JS api client with the response that is returned by the One-Tap sign in.

    Luckily we don't need to authenticate with the gapi JS client because we use a handy function called gapi.auth2.authorize!

    How to authorize the gapi client

    It's important to first read the docs and understand their warning:

    Do not use this method alongside the recommended gapi.auth2.init and signIn flow. These are two distinct behaviors (Authorization for gapi.auth2.authorize vs Authentication for gapi.auth2.init/signIn) and will have unexpected issues if used within the same application.

    Now the question is how to completely avoid the init/signIn method.

    Step 1
    Sign the user into the Google One-Tap sign in.

    Step 2
    Load the gapi client: window.gapi.load('client')

    Step 3
    Pass the credential.id (the email address) returned by Google One-Tap as the login_hint param in the call to authorize. This will preselect the account and we can try to not show any new login pop-up (prompt).

    Example:

    gapi.auth2.authorize({
      client_id,
      prompt: 'none',
      response_type: 'permission', // Access Token.
      scope: 'CALENDAR_SCOPE',
      login_hint: credential.id
    }, function(result) {})
    

    Using prompt: 'none', you can try to fetch a token without any UI. This is useful to check whether you need to show an Authorize button, and also useful before making any call to the Calendar API to get a fresh token.

    Step 4
    Before we can make any call to gapi.client.calendar we need to initialize the client with just the calendar discoveryDocs.

    gapi.client.init({discoveryDocs})
    

    This is the most tricky part and is not properly documented anywhere! The only thing we can retrieve from the api.client.init() documentation is the following line:

    If OAuth client ID and scope are provided, this function will load the gapi.auth2 module to perform OAuth

    This basically means: as soon as you give either clientID or scope gapi.client.init will try and start the traditional authentication method.
    In our case: we don't need to pass the clientID or scope as we've already done this in step 3.

    So how does the client know which module to initialize? → By only passing the discoveryDocs of the module you want to use! In this case the calendar discoveryDocs.

    Step 5
    Now you're done! You can make requests with e.g. gapi.client.calendar.events.list()


    Full example

    A full code example can be found here below:

    const config =  {
      response_type: 'permission',
      scope: 'CALENDAR_SCOPE',
      client_id: clientId,
      login_hint: credential.id,
      promt: 'none',
    }
    gapi.auth2.authorize(config, function(response) {
      // No need to `setToken`, it's done for you by auth2.
      let calConfig = {discoveryDocs} // only of google calendar
      window.gapi.client.init(calConfig).then(function() {
        // then execute a calendar call:
        window.gapi.client.calendar.events.list({'calendarId': 'primary'})
      })
    })