Migrate to Google Identity Services

Overview

To obtain a per user access token to call Google APIs, Google offers multiple JavaScript libraries:

This guide provides instructions to migrate from these libraries to the Google Identity Services library.

By following this guide, you will:

  • replace the deprecated Platform Library with the Identity Services library, and
  • if using the API Client Library, remove the deprecated gapi.auth2 module, its methods and objects, replacing them with Identity Services equivalents.

For a description of what has changed with the Identity Services JavaScript library read the overview and how user authorization works to review key terms and concepts.

If you are looking for authentication for user sign-up and sign-in see Migrating from Google Sign-In instead.

Identify your authorization flow

There are two possible user authorization flows: implicit and authorization code.

Review your web app to identify the type of authorization flow currently being used.

Indications your web app is using the implicit flow:

  • Your web app is purely browser based, with no backend platform.
  • The user must be present to call Google APIs, your app uses only access tokens, and does not require refresh tokens.
  • Your web app loads apis.google.com/js/api.js.
  • Your implementation is based upon OAuth 2.0 for Client-side Web Applications.
  • Your app uses either the gapi.client or gapi.auth2 modules found in Google API Client Library for JavaScript.

Indications your web app is using the authorization code flow:

  • Your implementation is based upon:

  • Your app executes both in the user's browser, and on your backend platform.

  • Your backend platform hosts an authorization code endpoint.

  • Your backend platform calls Google APIs on behalf of users without requiring them to be present, also known as offline mode.

  • Refresh tokens are managed and stored by your backend platform.

In some cases, your codebase might support both flows.

Choose an authorization flow

Prior to beginning your migration you need to determine if continuing with your existing flow or adopting a different flow best meets your needs.

Review choosing an authorization flow to understand the key differences and tradeoffs between the two flows.

In most cases, the authorization code flow is recommended as it offers the highest level of user security. Implementing this flow also enables your platform to more easily add new offline functionalities such as fetching updates to notify users of notable changes to their calendar, photos, subscriptions, and so on.

Choose an authorization flow using the selectors below.

Implicit flow

Obtain an access token for in-browser use while the user is present.

Implicit flow examples shows web apps before and after migration to Identity Services.

Authorization code flow

A per user authorization code issued by Google is delivered to your backend platform, where it is then exchanged for an access token and refresh token.

Authorization code flow examples shows web apps before and after migration to Identity Services.

Throughout this guide, follow the instructions listed in bold to Add, Remove, Update, or Replace existing functionality.

Changes to your in-browser web app

This section reviews the changes you will make to your in-browser web app when migrating to the Google Identity Services JavaScript library.

Identifying affected code and testing

A debug cookie can help to locate affected code and to test post-deprecation behavior.

In large or complex apps, it may be difficult to find all code affected by the deprecation of the gapi.auth2 module. To log existing use of soon to be deprecated functionality to the console, set the value of the G_AUTH2_MIGRATION cookie to informational. Optionally, add a colon followed by a key value to also log to session storage. After sign-in and receipt of credentials review or send collected logs to a backend for later analysis. For example, informational:showauth2use saves origin and URL to a session storage key named showauth2use.

To verify app behavior when the gapi.auth2 module is no longer loaded, set the value of the G_AUTH2_MIGRATION cookie to enforced. This enables testing of post-deprecation behavior in advance of the enforcement date.

Possible G_AUTH2_MIGRATION cookie values:

  • enforced Do not load gapi.auth2 module.
  • informational Log use of deprecated functionality to JS console. Also log to session storage when an optional key name is set: informational:key-name.

To minimize user impact it is recommended that you first set this cookie locally during development and test, before using it in production environments.

Libraries and modules

The gapi.auth2 module manages user authentication for sign-in and the implicit flow for authorization, replace this deprecated module, and its objects and methods with the Google Identity Services library.

Add the Identity Services library to your web app by including it in your document:

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

Remove any instances of loading the auth2 module using gapi.load('auth2', function).

The Google Identity Services library replaces usage of the gapi.auth2 module. You can safely continue using the gapi.client module from the Google API Client Library for JavaScript, and take advantage of its automatic creation of callable JS methods from a discovery document, batching multiple API calls, and CORS management functionality.

Cookies

User authorization does not require the use of cookies.

See Migrating from Google Sign-In for details of how user authentication makes use of cookies, and How Google uses cookies for cookie use by other Google products and services.

Credentials

Google Identity Services separates user authentication and authorization into two distinct operations, and user credentials are separate: the ID token used to identify a user is returned separately from the access token used for authorization.

To view these changes, see example credentials.

Implicit flow

Separate user authentication and authorization by removing user profile handling from authorization flows.

Remove these Google Sign-In JavaScript client references:

Methods

  • GoogleUser.getBasicProfile()
  • GoogleUser.getId()

Authorization code flow

Identity Services separates in-browser credentials into ID token and access token. This change does not apply to credentials obtained through direct calls to Google OAuth 2.0 endpoints from your backend platform or through libraries running on a secure server on your platform such as the Google APIs Node.js Client.

Session state

Previously, Google Sign-In helped you to manage user signed-in status using:

You are responsible for managing sign-in state and user sessions to your web app.

Remove these Google Sign-In JavaScript client references:

Objects:

  • gapi.auth2.SignInOptions

Methods:

  • GoogleAuth.attachClickHandler()
  • GoogleAuth.isSignedIn()
  • GoogleAuth.isSignedIn.get()
  • GoogleAuth.isSignedIn.listen()
  • GoogleAuth.signIn()
  • GoogleAuth.signOut()
  • GoogleAuth.currentUser.get()
  • GoogleAuth.currentUser.listen()
  • GoogleUser.isSignedIn()

Client configuration

Update your web app to initialize a token client for the implicit or authorization code flow.

Remove these Google Sign-In JavaScript client references:

Objects:

  • gapi.auth2.ClientConfig
  • gapi.auth2.OfflineAccessOptions

Methods:

  • gapi.auth2.getAuthInstance()
  • GoogleUser.grant()

Implicit flow

Add a TokenClientConfig object and initTokenClient() call to configure your web app, following the example in initialize a token client.

Replace Google Sign-In JavaScript client references with Google Identity Services:

Objects:

  • gapi.auth2.AuthorizeConfig with TokenClientConfig

Methods:

  • gapi.auth2.init() with google.accounts.oauth2.initTokenClient()

Parameters:

  • gapi.auth2.AuthorizeConfig.login_hint with TokenClientConfig.login_hint.
  • gapi.auth2.GoogleUser.getHostedDomain() with TokenClientConfig.hd.

Authorization code flow

Add a CodeClientConfig object and initCodeClient() call to configure your web app, following the example in initialize a Code Client.

When switching from the implicit to the authorization code flow:

Remove Google Sign-In JavaScript client references

Objects:

  • gapi.auth2.AuthorizeConfig

Methods:

  • gapi.auth2.init()

Parameters:

  • gapi.auth2.AuthorizeConfig.login_hint
  • gapi.auth2.GoogleUser.getHostedDomain()

Token request

A user gesture, such as a button click, generates a request that results in an access token being returned directly to the user's browser with the implicit flow, or to your backend platform after exchanging a per user authorization code for an access token and refresh token.

Implicit flow

Access tokens may be obtained and used in-browser while the user is signed-in and has an active session with Google. For implicit mode, a user gesture is required to request an access token, even if there was a prior request.

Replace Google Sign-In JavaScript client references: with Google Identity Services:

Methods:

  • gapi.auth2.authorize() with TokenClient.requestAccessToken()
  • GoogleUser.reloadAuthResponse() with TokenClient.requestAccessToken()

Add a link or button to call requestAccessToken() to initiate the pop-up UX flow to request an access token, or to obtain a new token when the existing token expires.

Update your codebase to:

  • Trigger the OAuth 2.0 token flow with requestAccessToken().
  • Support incremental authorization by using requestAccessToken and OverridableTokenClientConfig to separate one request for many scopes into multiple smaller requests.
  • Request a new token when the existing token expires, or is revoked.

Working with multiple scopes may require structural changes to your codebase to request access to scopes only as they are needed rather than all at once, this is known as incremental authorization. Each request should contain as few scopes as possible, and ideally a single scope. See how to handle user consent for more on how to update your app for incremental authorization.

When an access token expires, the gapi.auth2 module automatically obtains a new, valid access token for your web app. For improved user security, this automatic token refresh process is not supported by the Google Identity Services library. Your web app must be updated to detect an expired access token and request a new one. See the Token handling section below for more.

Authorization code flow

Add a link or button to call requestCode() to request an authorization code from Google. For an example, see Trigger OAuth 2.0 Code Flow.

See the Token handling section below for more on how to respond to an expired or revoked access token.

Token handling

Add error handling to detect failed Google API calls when an expired or revoked access token is used, and to request a new, valid access token.

An HTTP status code of 401 Unauthorized and invalid_token error message is returned by Google APIs when an expired or revoked access token is used. For an example, see Invalid token response.

Expired tokens

Access tokens are short-lived, and often valid only for a few minutes.

Token revocation

At any time, a Google Account owner may revoke previously granted consent. Doing so invalidates existing access tokens and refresh tokens. Revocation may be triggered from your platform using revoke() or through a Google Account.

Replace Google Sign-In JavaScript client references: with Google Identity Services:

Methods:

  • getAuthInstance().disconnect() with google.accounts.oauth2.revoke()
  • GoogleUser.disconnect() with google.accounts.oauth2.revoke()

Call revoke when a user deletes their account on your platform, or wishes to remove consent to share data with your app.

Google displays a consent dialog to the user when either your web app or backend platform requests an access token. See example consent dialogs displayed by Google to users.

Prior to issuing an access token to your app, an existing and active Google session is required to prompt for user consent and record the result. The user may be required to sign-in to a Google Account if an existing session has not already been established.

User sign-in

Users may be signed into a Google Account in a separate browser tab, or natively through a browser or operating system. We recommend adding Sign In With Google to your site to establish an active session between a Google Account and the browser when the user first opens your app. Doing so offers these benefits:

  • Minimizes the number of times a user must sign-in, requesting an access token initiates the Google Account sign-in process if an active session does not already exist.
  • Direct use the JWT ID Token credential email field as the value of the login_hint parameter in CodeClientConfig or TokenClientConfig objects. This is especially helpful if your platform does not maintain a user account management system.
  • Lookup and associate a Google Account with an existing local user account on your platform, helping to minimize duplicate accounts on your platform.
  • When a new, local account is created, your sign-up dialogs and flow can be clearly separated from user authentication dialogs and flows, reducing the number of required steps and improving drop-off rate.

After sign-in, and before an access token is issued, users must provide consent for your application for the requested scopes.

After consent, an access token is returned along with a list of scopes approved or rejected by the user.

Granular permissions allow users to approve or deny individual scopes. When requesting access to multiple scopes, each scope is granted or rejected independent of the other scopes. Based upon user choice your app selectively enables features and functionality which depend upon an individual scope.

Implicit flow

Replace Google Sign-In JavaScript client references with Google Identity Services:

Objects:

  • gapi.auth2.AuthorizeResponse with TokenClient.TokenResponse
  • gapi.auth2.AuthResponse with TokenClient.TokenResponse

Methods:

  • GoogleUser.hasGrantedScopes() with google.accounts.oauth2.hasGrantedAllScopes()
  • GoogleUser.getGrantedScopes() with google.accounts.oauth2.hasGrantedAllScopes()

Remove Google Sign-In JavaScript client references:

Methods:

  • GoogleUser.getAuthResponse()

Update your web app with hasGrantedAllScopes() and hasGrantedAnyScope() by following this granular permissions example.

Authorization code flow

Update or Add an authorization code endpoint to your backend platform by following the instructions in auth code handling.

Update your platform to follow the steps described in the Use Code Model guide to validate the request and obtain an access token and refresh token.

Update your platform to selectively enable or disable features and functionalities based upon the individual scopes the user has approved by following the instructions for incremental authorization and examine scopes of access granted by the user.

Implicit flow examples

The old way

GAPI Client Library

Example of the Google API Client Library for JavaScript running in browser using a popup dialog for user consent.

The gapi.auth2 module is automatically loaded and used by gapi.client.init(), and so is hidden.

<!DOCTYPE html>
  <html>
    <head>
      <script src="https://apis.google.com/js/api.js"></script>
      <script>
        function start() {
          gapi.client.init({
            'apiKey': 'YOUR_API_KEY',
            'clientId': 'YOUR_CLIENT_ID',
            'scope': 'https://www.googleapis.com/auth/cloud-translation',
            'discoveryDocs': ['https://www.googleapis.com/discovery/v1/apis/translate/v2/rest'],
          }).then(function() {
            // Execute an API request which is returned as a Promise.
            // The method name language.translations.list comes from the API discovery.
            return gapi.client.language.translations.list({
              q: 'hello world',
              source: 'en',
              target: 'de',
            });
          }).then(function(response) {
            console.log(response.result.data.translations[0].translatedText);
          }, function(reason) {
            console.log('Error: ' + reason.result.error.message);
          });
        };

        // Load the JavaScript client library and invoke start afterwards.
        gapi.load('client', start);
      </script>
    </head>
    <body>
      <div id="results"></div>
    </body>
  </html>

JS Client Library

OAuth 2.0 for Client-side Web Applications running in browser using a popup dialog for user consent.

The gapi.auth2 module is loaded manually.

<!DOCTYPE html>
<html><head></head><body>
<script>
  var GoogleAuth;
  var SCOPE = 'https://www.googleapis.com/auth/drive.metadata.readonly';
  function handleClientLoad() {
    // Load the API's client and auth2 modules.
    // Call the initClient function after the modules load.
    gapi.load('client:auth2', initClient);
  }

  function initClient() {
    // In practice, your app can retrieve one or more discovery documents.
    var discoveryUrl = 'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest';

    // Initialize the gapi.client object, which app uses to make API requests.
    // Get API key and client ID from API Console.
    // 'scope' field specifies space-delimited list of access scopes.
    gapi.client.init({
        'apiKey': 'YOUR_API_KEY',
        'clientId': 'YOUR_CLIENT_ID',
        'discoveryDocs': [discoveryUrl],
        'scope': SCOPE
    }).then(function () {
      GoogleAuth = gapi.auth2.getAuthInstance();

      // Listen for sign-in state changes.
      GoogleAuth.isSignedIn.listen(updateSigninStatus);

      // Handle initial sign-in state. (Determine if user is already signed in.)
      var user = GoogleAuth.currentUser.get();
      setSigninStatus();

      // Call handleAuthClick function when user clicks on
      //      "Sign In/Authorize" button.
      $('#sign-in-or-out-button').click(function() {
        handleAuthClick();
      });
      $('#revoke-access-button').click(function() {
        revokeAccess();
      });
    });
  }

  function handleAuthClick() {
    if (GoogleAuth.isSignedIn.get()) {
      // User is authorized and has clicked "Sign out" button.
      GoogleAuth.signOut();
    } else {
      // User is not signed in. Start Google auth flow.
      GoogleAuth.signIn();
    }
  }

  function revokeAccess() {
    GoogleAuth.disconnect();
  }

  function setSigninStatus() {
    var user = GoogleAuth.currentUser.get();
    var isAuthorized = user.hasGrantedScopes(SCOPE);
    if (isAuthorized) {
      $('#sign-in-or-out-button').html('Sign out');
      $('#revoke-access-button').css('display', 'inline-block');
      $('#auth-status').html('You are currently signed in and have granted ' +
          'access to this app.');
    } else {
      $('#sign-in-or-out-button').html('Sign In/Authorize');
      $('#revoke-access-button').css('display', 'none');
      $('#auth-status').html('You have not authorized this app or you are ' +
          'signed out.');
    }
  }

  function updateSigninStatus() {
    setSigninStatus();
  }
</script>

<button id="sign-in-or-out-button"
        style="margin-left: 25px">Sign In/Authorize</button>
<button id="revoke-access-button"
        style="display: none; margin-left: 25px">Revoke access</button>

<div id="auth-status" style="display: inline; padding-left: 25px"></div><hr>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script async defer src="https://apis.google.com/js/api.js"
        onload="this.onload=function(){};handleClientLoad()"
        onreadystatechange="if (this.readyState === 'complete') this.onload()">
</script>
</body></html>

OAuth 2.0 Endpoints

OAuth 2.0 for Client-side Web Applications running in browser using redirects to Google for user consent.

This example shows direct calls to Google's OAuth 2.0 endpoints from the user's browser and does not use the gapi.auth2 module or a JavaScript library.

<!DOCTYPE html>
<html><head></head><body>
<script>
  var YOUR_CLIENT_ID = 'REPLACE_THIS_VALUE';
  var YOUR_REDIRECT_URI = 'REPLACE_THIS_VALUE';
  var fragmentString = location.hash.substring(1);

  // Parse query string to see if page request is coming from OAuth 2.0 server.
  var params = {};
  var regex = /([^&=]+)=([^&]*)/g, m;
  while (m = regex.exec(fragmentString)) {
    params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
  }
  if (Object.keys(params).length > 0) {
    localStorage.setItem('oauth2-test-params', JSON.stringify(params) );
    if (params['state'] && params['state'] == 'try_sample_request') {
      trySampleRequest();
    }
  }

  // If there's an access token, try an API request.
  // Otherwise, start OAuth 2.0 flow.
  function trySampleRequest() {
    var params = JSON.parse(localStorage.getItem('oauth2-test-params'));
    if (params && params['access_token']) {
      var xhr = new XMLHttpRequest();
      xhr.open('GET',
          'https://www.googleapis.com/drive/v3/about?fields=user&' +
          'access_token=' + params['access_token']);
      xhr.onreadystatechange = function (e) {
        if (xhr.readyState === 4 && xhr.status === 200) {
          console.log(xhr.response);
        } else if (xhr.readyState === 4 && xhr.status === 401) {
          // Token invalid, so prompt for user permission.
          oauth2SignIn();
        }
      };
      xhr.send(null);
    } else {
      oauth2SignIn();
    }
  }

  /*

    *   Create form to request access token from Google's OAuth 2.0 server.
 */
function oauth2SignIn() {
  // Google's OAuth 2.0 endpoint for requesting an access token
  var oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';

    // Create element to open OAuth 2.0 endpoint in new window.
    var form = document.createElement('form');
    form.setAttribute('method', 'GET'); // Send as a GET request.
    form.setAttribute('action', oauth2Endpoint);

    // Parameters to pass to OAuth 2.0 endpoint.
    var params = {'client_id': YOUR_CLIENT_ID,
                  'redirect_uri': YOUR_REDIRECT_URI,
                  'scope': 'https://www.googleapis.com/auth/drive.metadata.readonly',
                  'state': 'try_sample_request',
                  'include_granted_scopes': 'true',
                  'response_type': 'token'};

    // Add form parameters as hidden input values.
    for (var p in params) {
      var input = document.createElement('input');
      input.setAttribute('type', 'hidden');
      input.setAttribute('name', p);
      input.setAttribute('value', params[p]);
      form.appendChild(input);
    }

    // Add form to page and submit it to open the OAuth 2.0 endpoint.
    document.body.appendChild(form);
    form.submit();
  }
</script>

<button onclick="trySampleRequest();">Try sample request</button>
</body></html>

The new way

GIS only

This example shows only the Google Identity Service JavaScript library using the token model and popup dialog for user consent. It is provided to illustrate the minimal number of steps required to configure a client, request and obtain an access token, and to call a Google API.

<!DOCTYPE html>
<html>
  <head>
    <script src="https://accounts.google.com/gsi/client" onload="initClient()" async defer></script>
  </head>
  <body>
    <script>
      var client;
      var access_token;

      function initClient() {
        client = google.accounts.oauth2.initTokenClient({
          client_id: 'YOUR_CLIENT_ID',
          scope: 'https://www.googleapis.com/auth/calendar.readonly \
                  https://www.googleapis.com/auth/contacts.readonly',
          callback: (tokenResponse) => {
            access_token = tokenResponse.access_token;
          },
        });
      }
      function getToken() {
        client.requestAccessToken();
      }
      function revokeToken() {
        google.accounts.oauth2.revoke(access_token, () => {console.log('access token revoked')});
      }
      function loadCalendar() {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', 'https://www.googleapis.com/calendar/v3/calendars/primary/events');
        xhr.setRequestHeader('Authorization', 'Bearer ' + access_token);
        xhr.send();
      }
    </script>
    <h1>Google Identity Services Authorization Token model</h1>
    <button onclick="getToken();">Get access token</button><br><br>
    <button onclick="loadCalendar();">Load Calendar</button><br><br>
    <button onclick="revokeToken();">Revoke token</button>
  </body>
</html>

GAPI async/await

This example shows how to add the Google Identity Service library using the token model, remove the gapi.auth2 module, and call an API using the Google API Client Library for JavaScript.

Promises, async and await are used to enforce library loading order and to catch and retry authorization errors. An API call is made only after a valid access token is available.

Users are expected to press the 'Show Calendar' button when the access token is missing when the page is first loaded, or later after the access token has expired.

<!DOCTYPE html>
<html>
<head></head>
<body>
  <h1>GAPI with GIS async/await</h1>
  <button id="showEventsBtn" onclick="showEvents();">Show Calendar</button><br><br>
  <button id="revokeBtn" onclick="revokeToken();">Revoke access token</button>

  <script>

    const gapiLoadPromise = new Promise((resolve, reject) => {
      gapiLoadOkay = resolve;
      gapiLoadFail = reject;
    });
    const gisLoadPromise = new Promise((resolve, reject) => {
      gisLoadOkay = resolve;
      gisLoadFail = reject;
    });

    var tokenClient;

    (async () => {
      document.getElementById("showEventsBtn").style.visibility="hidden";
      document.getElementById("revokeBtn").style.visibility="hidden";

      // First, load and initialize the gapi.client
      await gapiLoadPromise;
      await new Promise((resolve, reject) => {
        // NOTE: the 'auth2' module is no longer loaded.
        gapi.load('client', {callback: resolve, onerror: reject});
      });
      await gapi.client.init({
        // NOTE: OAuth2 'scope' and 'client_id' parameters have moved to initTokenClient().
      })
      .then(function() {  // Load the Calendar API discovery document.
        gapi.client.load('https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest');
      });

      // Now load the GIS client
      await gisLoadPromise;
      await new Promise((resolve, reject) => {
        try {
          tokenClient = google.accounts.oauth2.initTokenClient({
              client_id: 'YOUR_CLIENT_ID',
              scope: 'https://www.googleapis.com/auth/calendar.readonly',
              prompt: 'consent',
              callback: '',  // defined at request time in await/promise scope.
          });
          resolve();
        } catch (err) {
          reject(err);
        }
      });

      document.getElementById("showEventsBtn").style.visibility="visible";
      document.getElementById("revokeBtn").style.visibility="visible";
    })();

    async function getToken(err) {

      if (err.result.error.code == 401 || (err.result.error.code == 403) &&
          (err.result.error.status == "PERMISSION_DENIED")) {

        // The access token is missing, invalid, or expired, prompt for user consent to obtain one.
        await new Promise((resolve, reject) => {
          try {
            // Settle this promise in the response callback for requestAccessToken()
            tokenClient.callback = (resp) => {
              if (resp.error !== undefined) {
                reject(resp);
              }
              // GIS has automatically updated gapi.client with the newly issued access token.
              console.log('gapi.client access token: ' + JSON.stringify(gapi.client.getToken()));
              resolve(resp);
            };
            tokenClient.requestAccessToken();
          } catch (err) {
            console.log(err)
          }
        });
      } else {
        // Errors unrelated to authorization: server errors, exceeding quota, bad requests, and so on.
        throw new Error(err);
      }
    }

    function showEvents() {

      // Try to fetch a list of Calendar events. If a valid access token is needed,
      // prompt to obtain one and then retry the original request.
      gapi.client.calendar.events.list({ 'calendarId': 'primary' })
      .then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse)))
      .catch(err  => getToken(err))  // for authorization errors obtain an access token
      .then(retry => gapi.client.calendar.events.list({ 'calendarId': 'primary' }))
      .then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse)))
      .catch(err  => console.log(err)); // cancelled by user, timeout, etc.
    }

    function revokeToken() {
      let cred = gapi.client.getToken();
      if (cred !== null) {
        google.accounts.oauth2.revoke(cred.access_token, () => {console.log('Revoked: ' + cred.access_token)});
        gapi.client.setToken('');
      }
    }

  </script>

  <script async defer src="https://apis.google.com/js/api.js" onload="gapiLoadOkay()" onerror="gapiLoadFail(event)"></script>
  <script async defer src="https://accounts.google.com/gsi/client" onload="gisLoadOkay()" onerror="gisLoadFail(event)"></script>

</body>
</html>

GAPI callback

This example shows how to add the Google Identity Service library using the token model, remove the gapi.auth2 module, and call an API using the Google API Client Library for JavaScript.

Variables are used to enforce library loading order. GAPI calls are made from the within the callback after a valid access token is returned.

Users are expected to press the Show Calendar button when the page is first loaded and again when they'd like to refresh their Calendar info.

<!DOCTYPE html>
<html>
<head>
  <script async defer src="https://apis.google.com/js/api.js" onload="gapiLoad()"></script>
  <script async defer src="https://accounts.google.com/gsi/client" onload="gisInit()"></script>
</head>
<body>
  <h1>GAPI with GIS callbacks</h1>
  <button id="showEventsBtn" onclick="showEvents();">Show Calendar</button><br><br>
  <button id="revokeBtn" onclick="revokeToken();">Revoke access token</button>
  <script>
    let tokenClient;
    let gapiInited;
    let gisInited;

    document.getElementById("showEventsBtn").style.visibility="hidden";
    document.getElementById("revokeBtn").style.visibility="hidden";

    function checkBeforeStart() {
       if (gapiInited && gisInited){
          // Start only when both gapi and gis are initialized.
          document.getElementById("showEventsBtn").style.visibility="visible";
          document.getElementById("revokeBtn").style.visibility="visible";
       }
    }

    function gapiInit() {
      gapi.client.init({
        // NOTE: OAuth2 'scope' and 'client_id' parameters have moved to initTokenClient().
      })
      .then(function() {  // Load the Calendar API discovery document.
        gapi.client.load('https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest');
        gapiInited = true;
        checkBeforeStart();
      });
    }

    function gapiLoad() {
        gapi.load('client', gapiInit)
    }

    function gisInit() {
     tokenClient = google.accounts.oauth2.initTokenClient({
                client_id: 'YOUR_CLIENT_ID',
                scope: 'https://www.googleapis.com/auth/calendar.readonly',
                callback: '',  // defined at request time
            });
      gisInited = true;
      checkBeforeStart();
    }

    function showEvents() {

      tokenClient.callback = (resp) => {
        if (resp.error !== undefined) {
          throw(resp);
        }
        // GIS has automatically updated gapi.client with the newly issued access token.
        console.log('gapi.client access token: ' + JSON.stringify(gapi.client.getToken()));

        gapi.client.calendar.events.list({ 'calendarId': 'primary' })
        .then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse)))
        .catch(err => console.log(err));

        document.getElementById("showEventsBtn").innerText = "Refresh Calendar";
      }

      // Conditionally ask users to select the Google Account they'd like to use,
      // and explicitly obtain their consent to fetch their Calendar.
      // NOTE: To request an access token a user gesture is necessary.
      if (gapi.client.getToken() === null) {
        // Prompt the user to select a Google Account and asked for consent to share their data
        // when establishing a new session.
        tokenClient.requestAccessToken({prompt: 'consent'});
      } else {
        // Skip display of account chooser and consent dialog for an existing session.
        tokenClient.requestAccessToken({prompt: ''});
      }
    }

    function revokeToken() {
      let cred = gapi.client.getToken();
      if (cred !== null) {
        google.accounts.oauth2.revoke(cred.access_token, () => {console.log('Revoked: ' + cred.access_token)});
        gapi.client.setToken('');
        document.getElementById("showEventsBtn").innerText = "Show Calendar";
      }
    }
  </script>
</body>
</html>

Authorization code flow examples

The Google Identity Service library pop-up UX can either use a URL redirect to return an authorization code directly to your backend token endpoint, or a JavaScript callback handler running in the user's browser which proxies the response to your platform. In either case, your backend platform will complete the OAuth 2.0 flow to obtain a valid refresh and access token.

The old way

Server-side Web Apps

Google Sign-In for server-side apps running in on backend platform using a redirect to Google for user consent.

<!DOCTYPE html>
<html>
  <head>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
    <script src="https://apis.google.com/js/client:platform.js?onload=start" async defer></script>
    <script>
      function start() {
        gapi.load('auth2', function() {
          auth2 = gapi.auth2.init({
            client_id: 'YOUR_CLIENT_ID',
            api_key: 'YOUR_API_KEY',
            discovery_docs: ['https://www.googleapis.com/discovery/v1/apis/translate/v2/rest'],
            // Scopes to request in addition to 'profile' and 'email'
            scope: 'https://www.googleapis.com/auth/cloud-translation',
          });
        });
      }
      function signInCallback(authResult) {
        if (authResult['code']) {
          console.log("sending AJAX request");
          // Send authorization code obtained from Google to backend platform
          $.ajax({
            type: 'POST',
            url: 'YOUR_AUTHORIZATION_CODE_ENDPOINT_URL',
            // Always include an X-Requested-With header to protect against CSRF attacks.
            headers: {
              'X-Requested-With': 'XMLHttpRequest'
            },
            contentType: 'application/octet-stream; charset=utf-8',
            success: function(result) {
              console.log(result);
            },
            processData: false,
            data: authResult['code']
          });
        } else {
          console.log('error: failed to obtain authorization code')
        }
      }
    </script>
  </head>
  <body>
    <button id="signinButton">Sign In With Google</button>
    <script>
      $('#signinButton').click(function() {
        // Obtain an authorization code from Google
        auth2.grantOfflineAccess().then(signInCallback);
      });
    </script>
  </body>
</html>

HTTP/REST using redirect

Using OAuth 2.0 for Web Server Applications to send authorization code from the user's browser to your backend platform. User consent handled by redirecting the user's browser to Google.

/\*
 \* Create form to request access token from Google's OAuth 2.0 server.
 \*/
function oauthSignIn() {
  // Google's OAuth 2.0 endpoint for requesting an access token
  var oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
  // Create &lt;form> element to submit parameters to OAuth 2.0 endpoint.
  var form = document.createElement('form');
  form.setAttribute('method', 'GET'); // Send as a GET request.
  form.setAttribute('action', oauth2Endpoint);
  // Parameters to pass to OAuth 2.0 endpoint.
  var params = {'client\_id': 'YOUR_CLIENT_ID',
                'redirect\_uri': 'YOUR_AUTHORIZATION_CODE_ENDPOINT_URL',
                'response\_type': 'token',
                'scope': 'https://www.googleapis.com/auth/drive.metadata.readonly',
                'include\_granted\_scopes': 'true',
                'state': 'pass-through value'};
  // Add form parameters as hidden input values.
  for (var p in params) {
    var input = document.createElement('input');
    input.setAttribute('type', 'hidden');
    input.setAttribute('name', p);
    input.setAttribute('value', params[p]);
    form.appendChild(input);
  }

  // Add form to page and submit it to open the OAuth 2.0 endpoint.
  document.body.appendChild(form);
  form.submit();
}

The new way

GIS Popup UX

This example shows only the Google Identity Service JavaScript library using the authorization code model a popup dialog for user consent and callback handler to receive the authorization code from Google. It is provided to illustrate the minimal number of steps required to configure a client, obtain consent and send an authorization code to your backend platform.

<!DOCTYPE html>
<html>
  <head>
    <script src="https://accounts.google.com/gsi/client" onload="initClient()" async defer></script>
  </head>
  <body>
    <script>
      var client;
      function initClient() {
        client = google.accounts.oauth2.initCodeClient({
          client_id: 'YOUR_CLIENT_ID',
          scope: 'https://www.googleapis.com/auth/calendar.readonly',
          ux_mode: 'popup',
          callback: (response) => {
            var code_receiver_uri = 'YOUR_AUTHORIZATION_CODE_ENDPOINT_URI',
            // Send auth code to your backend platform
            const xhr = new XMLHttpRequest();
            xhr.open('POST', code_receiver_uri, true);
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
            xhr.onload = function() {
              console.log('Signed in as: ' + xhr.responseText);
            };
            xhr.send('code=' + response.code);
            // After receipt, the code is exchanged for an access token and
            // refresh token, and the platform then updates this web app
            // running in user's browser with the requested calendar info.
          },
        });
      }
      function getAuthCode() {
        // Request authorization code and obtain user consent
        client.requestCode();
      }
    </script>
    <button onclick="getAuthCode();">Load Your Calendar</button>
  </body>
</html>

GIS Redirect UX

Authorization code model supports the popup and redirect UX modes to send a per user authorization code to the endpoint hosted by your platform. The redirect UX mode is shown here:

<!DOCTYPE html>
<html>
  <head>
    <script src="https://accounts.google.com/gsi/client" onload="initClient()" async defer></script>
  </head>
  <body>
    <script>
      var client;
      function initClient() {
        client = google.accounts.oauth2.initCodeClient({
          client_id: 'YOUR_CLIENT_ID',
          scope: 'https://www.googleapis.com/auth/calendar.readonly \
                  https://www.googleapis.com/auth/photoslibrary.readonly',
          ux_mode: 'redirect',
          redirect_uri: 'YOUR_AUTHORIZATION_CODE_ENDPOINT_URI'
        });
      }
      // Request an access token
      function getAuthCode() {
        // Request authorization code and obtain user consent
        client.requestCode();
      }
    </script>
    <button onclick="getAuthCode();">Load Your Calendar</button>
  </body>
</html>

JavaScript libraries

Google Identity Services is a single JavaScript library used for user authentication and authorization that consolidates and replaces features and functionality found in multiple different libraries and modules:

Actions to take when migrating to Identity Services:

Existing JS library New JS library Notes
apis.google.com/js/api.js accounts.google.com/gsi/client Add new library and follow the implicit flow.
apis.google.com/js/client.js accounts.google.com/gsi/client Add new library and the authorization code flow.

Library quick reference

Object and method comparison between the Old Google Sign-In JavaScript client library and the New Google Identity Services library and Notes with additional information and action to take during migration.

Old New Notes
GoogleAuth object and associated methods:
GoogleAuth.attachClickHandler() Remove
GoogleAuth.currentUser.get() Remove
GoogleAuth.currentUser.listen() Remove
GoogleAuth.disconnect() google.accounts.oauth2.revoke Replace old with new. Revocation may also occur from https://myaccount.google.com/permissions
GoogleAuth.grantOfflineAccess() Remove, follow the authorization code flow.
GoogleAuth.isSignedIn.get() Remove
GoogleAuth.isSignedIn.listen() Remove
GoogleAuth.signIn() Remove
GoogleAuth.signOut() Remove
GoogleAuth.then() Remove
GoogleUser object and associated methods:
GoogleUser.disconnect() google.accounts.id.revoke Replace old with new. Revocation may also occur from https://myaccount.google.com/permissions
GoogleUser.getAuthResponse() requestCode() or requestAccessToken() Replace old with new
GoogleUser.getBasicProfile() Remove. Use ID Token instead, see Migrating from Google Sign-In.
GoogleUser.getGrantedScopes() hasGrantedAnyScope() Replace old with new
GoogleUser.getHostedDomain() Remove
GoogleUser.getId() Remove
GoogleUser.grantOfflineAccess() Remove, follow the authorization code flow.
GoogleUser.grant() Remove
GoogleUser.hasGrantedScopes() hasGrantedAnyScope() Replace old with new
GoogleUser.isSignedIn() Remove
GoogleUser.reloadAuthResponse() requestAccessToken() Remove old, call new to replace expired or revoked access token.
gapi.auth2 object and associated methods:
gapi.auth2.AuthorizeConfig object TokenClientConfig or CodeClientConfig Replace old with new
gapi.auth2.AuthorizeResponse object Remove
gapi.auth2.AuthResponse object Remove
gapi.auth2.authorize() requestCode() or requestAccessToken() Replace old with new
gapi.auth2.ClientConfig() TokenClientConfig or CodeClientConfig Replace old with new
gapi.auth2.getAuthInstance() Remove
gapi.auth2.init() initTokenClient() or initCodeClient() Replace old with new
gapi.auth2.OfflineAccessOptions object Remove
gapi.auth2.SignInOptions object Remove
gapi.signin2 object and associated methods:
gapi.signin2.render() Remove. HTML DOM loading of the g_id_signin element or JS call to google.accounts.id.renderButton triggers user sign-in to a Google Account.

Example credentials

Existing credentials

The Google Sign-In platform library, Google API Client Library for JavaScript, or direct calls to Google Auth 2.0 endpoints return both an OAuth 2.0 access token and an OpenID Connect ID Token in a single response.

Example response containing both access_token and id_token:

  {
    "token_type": "Bearer",
    "access_token": "ya29.A0ARrdaM-SmArZaCIh68qXsZSzyeU-8mxhQERHrP2EXtxpUuZ-3oW8IW7a6D2J6lRnZrRj8S6-ZcIl5XVEqnqxq5fuMeDDH_6MZgQ5dgP7moY-yTiKR5kdPm-LkuPM-mOtUsylWPd1wpRmvw_AGOZ1UUCa6UD5Hg",
    "scope": "https://www.googleapis.com/auth/calendar.readonly",
    "login_hint": "AJDLj6I2d1RH77cgpe__DdEree1zxHjZJr4Q7yOisoumTZUmo5W2ZmVFHyAomUYzLkrluG-hqt4RnNxrPhArd5y6p8kzO0t8xIfMAe6yhztt6v2E-_Bb4Ec3GLFKikHSXNh5bI-gPrsI",
    "expires_in": 3599,
    "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjkzNDFhYmM0MDkyYjZmYzAzOGU0MDNjOTEwMjJkZDNlNDQ1MzliNTYiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiYXpwIjoiNTM4MzQ0NjUzMjU1LTc1OGM1aDVpc2M0NXZnazI3ZDhoOGRlYWJvdnBnNnRvLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXVkIjoiNTM4MzQ0NjUzMjU1LTc1OGM1aDVpc2M0NXZnazI3ZDhoOGRlYWJvdnBnNnRvLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwic3ViIjoiMTE3NzI2NDMxNjUxOTQzNjk4NjAwIiwiaGQiOiJnb29nbGUuY29tIiwiZW1haWwiOiJkYWJyaWFuQGdvb2dsZS5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6IkJBSW55TjN2MS1ZejNLQnJUMVo0ckEiLCJuYW1lIjoiQnJpYW4gRGF1Z2hlcnR5IiwicGljdHVyZSI6Imh0dHBzOi8vbGgzLmdvb2dsZXVzZXJjb250ZW50LmNvbS9hLS9BT2gxNEdnenAyTXNGRGZvbVdMX3VDemRYUWNzeVM3ZGtxTE5ybk90S0QzVXNRPXM5Ni1jIiwiZ2l2ZW5fbmFtZSI6IkJyaWFuIiwiZmFtaWx5X25hbWUiOiJEYXVnaGVydHkiLCJsb2NhbGUiOiJlbiIsImlhdCI6MTYzODk5MTYzOCwiZXhwIjoxNjM4OTk1MjM4LCJqdGkiOiI5YmRkZjE1YWFiNzE2ZDhjYmJmNDYwMmM1YWM3YzViN2VhMDQ5OTA5In0.K3EA-3Adw5HA7O8nJVCsX1HmGWxWzYk3P7ViVBb4H4BoT2-HIgxKlx1mi6jSxIUJGEekjw9MC-nL1B9Asgv1vXTMgoGaNna0UoEHYitySI23E5jaMkExkTSLtxI-ih2tJrA2ggfA9Ekj-JFiMc6MuJnwcfBTlsYWRcZOYVw3QpdTZ_VYfhUu-yERAElZCjaAyEXLtVQegRe-ymScra3r9S92TA33ylMb3WDTlfmDpWL0CDdDzby2asXYpl6GQ7SdSj64s49Yw6mdGELZn5WoJqG7Zr2KwIGXJuSxEo-wGbzxNK-mKAiABcFpYP4KHPEUgYyz3n9Vqn2Tfrgp-g65BQ",
    "session_state": {
      "extraQueryParams": {
        "authuser": "0"
      }
    },
    "first_issued_at": 1638991637982,
    "expires_at": 1638995236982,
    "idpId": "google"
  }

Google Identity Services credential

The Google Identity Services library returns:

  • either an access token when used for authorization:

    {
      "access_token": "ya29.A0ARrdaM_LWSO-uckLj7IJVNSfnUityT0Xj-UCCrGxFQdxmLiWuAosnAKMVQ2Z0LLqeZdeJii3TgULp6hR_PJxnInBOl8UoUwWoqsrGQ7-swxgy97E8_hnzfhrOWyQBmH6zs0_sUCzwzhEr_FAVqf92sZZHphr0g",
      "token_type": "Bearer",
      "expires_in": 3599,
      "scope": "https://www.googleapis.com/auth/calendar.readonly"
    }
    
  • or, an ID token when used for authentication:

    {
      "clientId": "538344653255-758c5h5isc45vgk27d8h8deabovpg6to.apps.googleusercontent.com",
      "credential": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImMxODkyZWI0OWQ3ZWY5YWRmOGIyZTE0YzA1Y2EwZDAzMjcxNGEyMzciLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJuYmYiOjE2MzkxNTcyNjQsImF1ZCI6IjUzODM0NDY1MzI1NS03NThjNWg1aXNjNDV2Z2syN2Q4aDhkZWFib3ZwZzZ0by5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsInN1YiI6IjExNzcyNjQzMTY1MTk0MzY5ODYwMCIsIm5vbmNlIjoiZm9vYmFyIiwiaGQiOiJnb29nbGUuY29tIiwiZW1haWwiOiJkYWJyaWFuQGdvb2dsZS5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXpwIjoiNTM4MzQ0NjUzMjU1LTc1OGM1aDVpc2M0NXZnazI3ZDhoOGRlYWJvdnBnNnRvLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwibmFtZSI6IkJyaWFuIERhdWdoZXJ0eSIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS0vQU9oMTRHZ3pwMk1zRkRmb21XTF91Q3pkWFFjc3lTN2RrcUxOcm5PdEtEM1VzUT1zOTYtYyIsImdpdmVuX25hbWUiOiJCcmlhbiIsImZhbWlseV9uYW1lIjoiRGF1Z2hlcnR5IiwiaWF0IjoxNjM5MTU3NTY0LCJleHAiOjE2MzkxNjExNjQsImp0aSI6IjRiOTVkYjAyZjU4NDczMmUxZGJkOTY2NWJiMWYzY2VhYzgyMmI0NjUifQ.Cr-AgMsLFeLurnqyGpw0hSomjOCU4S3cU669Hyi4VsbqnAV11zc_z73o6ahe9Nqc26kPVCNRGSqYrDZPfRyTnV6g1PIgc4Zvl-JBuy6O9HhClAK1HhMwh1FpgeYwXqrng1tifmuotuLQnZAiQJM73Gl-J_6s86Buo_1AIx5YAKCucYDUYYdXBIHLxrbALsA5W6pZCqqkMbqpTWteix-G5Q5T8LNsfqIu_uMBUGceqZWFJALhS9ieaDqoxhIqpx_89QAr1YlGu_UO6R6FYl0wDT-nzjyeF5tonSs3FHN0iNIiR3AMOHZu7KUwZaUdHg4eYkU-sQ01QNY_11keHROCRQ",
      "select_by": "user"
    }
    

Invalid token response

Example response from Google when attempting to make an API request using an expired, revoked, or invalid access token:

HTTP Response Headers

  www-authenticate: Bearer realm="https://accounts.google.com/", error="invalid_token"

Response body

  {
    "error": {
      "code": 401,
      "message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
      "errors": [
        {
          "message": "Invalid Credentials",
          "domain": "global",
          "reason": "authError",
          "location": "Authorization",
          "locationType": "header"
        }
      ],
      "status": "UNAUTHENTICATED"
    }
  }