> [!WARNING]
> **Warning:** The [Google Sign-In JavaScript platform library for Web](https://developers.google.com/identity/sign-in/web/sign-in) is deprecated. For migration help, see the [authentication](https://developers.googleblog.com/2021/08/gsi-jsweb-deprecation.html) and [authorization](https://developers.googleblog.com/2022/03/gis-jsweb-authz-migration.html) deprecation announcements and the [Deprecation and Sunset](https://developers.google.com/identity/sign-in/web/deprecation-and-sunset) guide.

## Overview

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

- [Google Sign-In Platform Library](https://developers.google.com/identity/sign-in/web/sign-in)
- [Google API Client Library for JavaScript](https://github.com/google/google-api-javascript-client)

This guide provides instructions to migrate from these libraries to the [Google
Identity Services library](https://developers.google.com/identity/oauth2/web/guides/overview).

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](https://developers.google.com/identity/oauth2/web/guides/overview) and [how user authorization works](https://developers.google.com/identity/oauth2/web/guides/how-user-authz-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](https://developers.google.com/identity/gsi/web/guides/migration) instead.

> [!NOTE]
> **Note:** The `email`, `profile`, and `openid` are used for user authentication. If your app only uses these scopes [Sign In With Google](https://developers.google.com/identity/gsi/web) is recommended 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 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](https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow#js-client-library).
- Your app uses either the `gapi.client` or `gapi.auth2` modules found in [Google API Client Library for JavaScript](https://github.com/google/google-api-javascript-client).

Indications your web app is using the **authorization code flow**:

- Your implementation is based upon:

  - [Using OAuth 2.0 for Web Server Applications](https://developers.google.com/identity/protocols/oauth2/web-server) or
  - [Google Sign-In for server-side apps](https://developers.google.com/identity/sign-in/web/server-side-flow)
- 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.

> [!IMPORTANT]
> **Important:** if your app uses `apis.google.com/js/platform.js` to display the default Google Sign-In button, or `apis.google.com/js/api:client.js` to create a custom Google Sign-In button, follow [Migrating from Google Sign-In](https://developers.google.com/identity/gsi/web/guides/migration).

## 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](https://developers.google.com/identity/oauth2/web/guides/choose-authorization-model#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 add new offline functionalities such as fetching updates to notify
users of notable changes to their calendar, photos, and subscriptions.

**Choose** an authorization flow using the selectors.

> [!IMPORTANT]
> **Key Point:** The migration instructions specific to your chosen flow will be displayed throughout this guide based upon this choice. For the best balance of usability and security using the [Popup mode UX flow with Authorization code
> model](https://developers.google.com/identity/oauth2/web/guides/migration-to-gis#the_new_way_2) is recommended.

### Implicit flow

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

[Implicit flow examples](https://developers.google.com/identity/oauth2/web/guides/migration-to-gis#implicit_flow_examples) shows web apps before and after migration to
Identity Services.

> [!NOTE]
> **Objective:** update your in-browser web application to use Google Identity Services objects and methods, remove `auth2` module dependencies, and work with incremental authorization and granular permissions.

### 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](https://developers.google.com/identity/oauth2/web/guides/migration-to-gis#authorization_code_flow_examples) shows web apps before and after
migration to Identity Services.

> [!NOTE]
> **Objective:** update your in-browser web application to use Google Identity Services objects and methods, remove `auth2` module dependencies, and update your backend platform with incremental authorization and granular permissions.

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.

### Identify affected code and test

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](https://developer.chrome.com/docs/devtools/storage/sessionstorage/). 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` Don't 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](https://github.com/google/google-api-javascript-client), and take advantage of its automatic creation
of callable JS methods from a discovery document, batching multiple API calls,
and CORS management functionality.

> [!IMPORTANT]
> **Important:** Both the Identity Services library and the Google API Client Library for JavaScript (gapi.client) are intended for use in browsers only. Including the Identity Services library in backend JavaScript frameworks is not supported, instead consider using the [Google APIs Node.js Client](https://github.com/googleapis/google-api-nodejs-client). library.

### Cookies

User authorization does not require the use of cookies.

See [Migrating from Google Sign-In](https://developers.google.com/identity/gsi/web/guides/migration#cookies) for details of how user authentication
makes use of cookies, and [How Google uses cookies](https://policies.google.com/technologies/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](https://developers.google.com/identity/oauth2/web/guides/migration-to-gis#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](https://developers.google.com/identity/sign-in/web/reference):

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](https://github.com/googleapis/google-api-nodejs-client).

### Session state

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

- Callback handlers for [Monitoring the user's session state](https://developers.google.com/identity/sign-in/web/session-state).
- [Listeners](https://developers.google.com/identity/sign-in/web/listeners) for events and changes to signed-in status for a user's Google Account.

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

**Remove** these [Google Sign-In JavaScript client references](https://developers.google.com/identity/sign-in/web/reference):

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](https://developers.google.com/identity/sign-in/web/reference):

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](https://developers.google.com/identity/oauth2/web/guides/use-token-model#initialize_a_token_client).

**Replace** [Google Sign-In JavaScript client references](https://developers.google.com/identity/sign-in/web/reference) with [Google
Identity Services](https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.initTokenClient):

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](https://developers.google.com/identity/oauth2/web/guides/use-code-model#initialize_a_code_client).

When switching from the implicit to the authorization code flow:

**Remove** [Google Sign-In JavaScript client references](https://developers.google.com/identity/sign-in/web/reference)

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

> [!NOTE]
> **Note:** Create a single reentrant function containing `requestAccessToken()`, call it when an access token is first needed, and later, after the existing token expires and Google API calls return a `401 Unauthorized` status code response.

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](https://developers.google.com/identity/sign-in/web/reference): with [Google
Identity Services](https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.initTokenClient):

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](https://developers.google.com/identity/oauth2/web/guides/use-token-model#trigger_the_oauth_20_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](https://developers.google.com/identity/oauth2/web/guides/use-token-model#how_to_handle_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 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](https://developers.google.com/identity/oauth2/web/guides/use-code-model#trigger_oauth_20_code_flow).

See the Token handling section 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](https://developers.google.com/identity/oauth2/web/guides/migration-to-gis#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](https://myaccount.google.com/permissions).

**Replace** [Google Sign-In JavaScript client references](https://developers.google.com/identity/sign-in/web/reference): with [Google
Identity Services](https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.initTokenClient):

Methods:

- `getAuthInstance().disconnect()` with `google.accounts.oauth2.revoke()`
- `GoogleUser.disconnect()` with `google.accounts.oauth2.revoke()`

Call [`revoke`](https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.revoke) when a user deletes their account on your platform, or
wishes to remove consent to share data with your app.

### User consent prompt

Google displays a consent dialog to the user when either your web app or backend
platform requests an access token. See example [consent dialogs](https://developers.google.com/identity/oauth2/web/guides/how-user-authz-works#user_consent) 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](https://developers.google.com/identity/gsi/web/guides/overview) 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 of the JWT ID token [credential](https://developers.google.com/identity/gsi/web/reference/js-reference#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.

### Token and consent response

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](https://developers.google.com/identity/sign-in/web/reference) with [Google
Identity Services](https://developers.google.com/identity/oauth2/web/reference/js-reference#google.accounts.oauth2.initTokenClient):

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](https://developers.google.com/identity/sign-in/web/reference):

Methods:

- `GoogleUser.getAuthResponse()`

**Update** your web app with `hasGrantedAllScopes()` and
`hasGrantedAnyScope()` by following this [granular permissions](https://developers.google.com/identity/oauth2/web/guides/use-token-model#granular_permissions) example.

> [!NOTE]
> **Note:** A `requestAccessToken()` response updates the `gapi.client` access token value cached in browser memory. This lets you request access tokens with the GIS library and to seamlessly use the returned access token in `gapi.client` calls such as `gapi.client.getToken()`.

### Authorization code flow

**Update** or **Add** an authorization code endpoint to your backend
platform by following the instructions in [auth code handling](https://developers.google.com/identity/oauth2/web/guides/use-code-model#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](https://developers.google.com/identity/protocols/oauth2/web-server#incrementalAuth) and [examine
scopes of access granted by the user](https://developers.google.com/identity/protocols/oauth2#scope-response).

> [!IMPORTANT]
> **Key Point:** It is expected that your backend platform will call Google APIs on behalf of your web app running in the user's browser, sharing an access token from your backend platform to your web app is out of scope of this migration guide.

## Implicit flow examples

#### The old way

### GAPI Client Library

Example of the [Google API Client Library for JavaScript](https://github.com/google/google-api-javascript-client/blob/master/docs/samples.md#loading-an-api-and-making-a-request)
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](https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow#js-client-library_4) 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 Google Cloud 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](https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow#oauth-2.0-endpoints_4) running in browser using
redirects to the Google authorization server 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.

> [!NOTE]
> **Note:** use the Google Identity Services library to support a less intrusive popup UX mode and to avoid having to manage complex OAuth 2.0 requests and responses.

    <!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 the Google authorization 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 the Google authorization 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](https://developers.google.com/identity/oauth2/web/guides/use-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](https://developers.google.com/identity/oauth2/web/guides/use-token-model), remove the `gapi.auth2` module, and call an API using the
[Google API Client Library for JavaScript](https://github.com/google/google-api-javascript-client/blob/master/docs/samples.md#loading-an-api-and-making-a-request).

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>
        <title>GAPI and GIS Example</title>
        <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="gisLoad()"></script>
    </head>
    <body>
        <h1>GAPI Client with GIS Authorization</h1>
        <button id="authorizeBtn" style="visibility:hidden;">Authorize and Load Events</button>
        <button id="revokeBtn" style="visibility:hidden;">Revoke Access</button>
        <div id="content"></div>

        <script>
            const YOUR_CLIENT_ID = "YOUR_CLIENT_ID";
            const YOUR_API_KEY = 'YOUR_API_KEY';
            const CALENDAR_SCOPE = 'https://www.googleapis.com/auth/calendar.readonly';

            let tokenClient;
            let libsLoaded = 0;

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

            async function initGapiClient() {
                try {
                    await gapi.client.init({ apiKey: YOUR_API_KEY });
                    await gapi.client.load('https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest');
                    console.log('GAPI client initialized.');
                    checkAllLoaded();
                } catch (err) {
                    handleError('GAPI initialization failed:', err);
                }
            }

            function gisLoad() {
                try {
                    tokenClient = google.accounts.oauth2.initTokenClient({
                        client_id: YOUR_CLIENT_ID,
                        scope: CALENDAR_SCOPE,
                        callback: '', // Will be set dynamically
                        error_callback: handleGisError,
                    });
                    console.log('GIS TokenClient initialized.');
                    checkAllLoaded();
                } catch (err) {
                    handleError('GIS initialization failed:', err);
                }
            }

            function checkAllLoaded() {
                libsLoaded++;
                if (libsLoaded === 2) {
                    document.getElementById('authorizeBtn').style.visibility = 'visible';
                    document.getElementById('revokeBtn').style.visibility = 'visible';
                    document.getElementById('authorizeBtn').onclick = makeApiCall;
                    document.getElementById('revokeBtn').onclick = revokeAccess;
                    console.log('Ready to authorize.');
                }
            }

            function handleGisError(err) {
                console.error('GIS Error:', err);
                let message = 'An error occurred during authorization.';
                if (err && err.type === 'popup_failed_to_open') {
                    message = 'Failed to open popup. Please disable popup blockers.';
                } else if (err && err.type === 'popup_closed') {
                    message = 'Authorization popup was closed.';
                }
                document.getElementById('content').textContent = message;
            }

            function handleError(message, error) {
                console.error(message, error);
                document.getElementById('content').textContent = `${message} ${error.message || JSON.stringify(error)}`;
            }

            async function makeApiCall() {
                document.getElementById('content').textContent = 'Processing...';
                try {
                    let token = gapi.client.getToken();
                    if (!token || !token.access_token) {
                        console.log('No token, fetching one...');
                        await getToken();
                    }

                    console.log('Calling Calendar API...');
                    const response = await gapi.client.calendar.events.list({ 'calendarId': 'primary' });
                    displayEvents(response.result);
                } catch (err) {
                    console.error('API call failed:', err);
                    const errorInfo = err.result && err.result.error;
                    if (errorInfo && (errorInfo.code === 401 || (errorInfo.code === 403 && errorInfo.status === "PERMISSION_DENIED"))) {
                        console.log('Auth error on API call, refreshing token...');
                        try {
                            await getToken({ prompt: 'consent' }); // Force refresh
                            const retryResponse = await gapi.client.calendar.events.list({ 'calendarId': 'primary' });
                            displayEvents(retryResponse.result);
                        } catch (refreshErr) {
                            handleError('Failed to refresh token or retry API call:', refreshErr);
                        }
                    } else {
                        handleError('Error loading events:', err.result ? err.result.error : err);
                    }
                }
            }

            async function getToken(options = { prompt: '' }) {
                return new Promise((resolve, reject) => {
                    if (!tokenClient) return reject(new Error("GIS TokenClient not initialized."));
                    tokenClient.callback = (tokenResponse) => {
                        if (tokenResponse.error) {
                            reject(new Error(`Token Error: ${tokenResponse.error} - ${tokenResponse.error_description}`));
                        } else {
                            console.log('Token acquired.');
                            resolve(tokenResponse);
                        }
                    };
                    tokenClient.requestAccessToken(options);
                });
            }

            function displayEvents(result) {
                const events = result.items;
                if (events && events.length > 0) {
                    let eventList = '<h3>Upcoming Events:</h3><ul>' + events.map(event =>
                        `<li>${event.summary} (${event.start.dateTime || event.start.date})</li>`
                    ).join('') + '</ul>';
                    document.getElementById('content').innerHTML = eventList;
                } else {
                    document.getElementById('content').textContent = 'No upcoming events found.';
                }
            }

            function revokeAccess() {
                const token = gapi.client.getToken();
                if (token && token.access_token) {
                    google.accounts.oauth2.revoke(token.access_token, () => {
                        console.log('Access revoked.');
                        document.getElementById('content').textContent = 'Access has been revoked.';
                        gapi.client.setToken(null);
                    });
                } else {
                    document.getElementById('content').textContent = 'No token to revoke.';
                }
            }
        </script>
    </body>
    </html>

### GAPI callback

This example shows how to add the Google Identity Service library [using the
token model](https://developers.google.com/identity/oauth2/web/guides/use-token-model), remove the `gapi.auth2` module, and call an API using the
[Google API Client Library for JavaScript](https://github.com/google/google-api-javascript-client/blob/master/docs/samples.md#loading-an-api-and-making-a-request).

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](https://developers.google.com/identity/sign-in/web/server-side-flow) 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](https://developers.google.com/identity/protocols/oauth2/web-server#httprest_5) to send authorization code
from the user's browser to your backend platform. User consent handled by
redirecting the user's browser to the Google authorization server.

    /*
     * Create form to request access token from the Google authorization 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](https://developers.google.com/identity/oauth2/web/guides/use-code-model#popup-mode) 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](https://developers.google.com/identity/oauth2/web/guides/use-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](https://developers.google.com/identity/oauth2/web/reference/js-reference) 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:

- [Google Sign-In JavaScript client](https://developers.google.com/identity/sign-in/web/reference) and
- [Google API Client Library for JavaScript](https://github.com/google/google-api-javascript-client).

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 follow the authorization code flow. |

## Library quick reference

Object and method comparison between the **Old** [Google Sign-In JavaScript
client](https://developers.google.com/identity/sign-in/web/reference) library and the **New** [Google Identity Services](https://developers.google.com/identity/oauth2/web/reference/js-reference) 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`](https://developers.google.com/identity/oauth2/web/reference/js-reference#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`](https://developers.google.com/identity/gsi/web/reference/js-reference#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](https://developers.google.com/identity/gsi/web/guides/migration#token_response). |
| `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`](https://developers.google.com/identity/gsi/web/reference/html-reference#element_with_id_g_id_onload) element or JS call to [`google.accounts.id.renderButton`](https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.renderButton) triggers user sign-in to a Google Account. |

## Example credentials

#### Existing credentials

The [Google Sign-In platform library](https://developers.google.com/identity/sign-in/web/sign-in), [Google API Client Library for
JavaScript](https://github.com/google/google-api-javascript-client), or direct calls to Google OAuth 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

> [!IMPORTANT]
> **Important:** This format applies only to the implicit flow and the Identity Services JavaScript library. Credentials obtained by the authorization code flow through direct calls to Google OAuth 2.0 endpoints from your backend platform return access token and ID token together in a single response.

The [Google Identity Services library](https://developers.google.com/identity/gsi/web) 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"
        }
      }