Переход на сервисы идентификации Google

Обзор

Чтобы получить токен доступа для каждого пользователя и вызвать API Google, можно использовать одну из нескольких библиотек JavaScript:

В этом руководстве рассказывается, как перейти с этих библиотек на библиотеку Сервисов идентификации Google.

Выполнив инструкции из этого руководства, вы:

  • заменить устаревшую библиотеку платформы на библиотеку сервисов идентификации;
  • Если вы используете клиентскую библиотеку API, удалите устаревший модуль gapi.auth2, его методы и объекты, заменив их эквивалентами сервиса идентификации.

Чтобы узнать, что изменилось в библиотеке JavaScript сервисов идентификации, прочитайте обзор и статью о том, как работает авторизация пользователей.

Если вам нужна аутентификация для регистрации и входа пользователей, ознакомьтесь со статьей Переход с входа с аккаунтом Google.

Определите процесс авторизации

Существует два возможных процесса авторизации пользователей: неявный и с кодом авторизации.

Проверьте свое веб-приложение, чтобы определить тип используемого процесса авторизации.

Признаки того, что ваше веб-приложение использует неявное предоставление доступа:

  • Ваше веб-приложение работает только в браузере и не имеет серверной платформы.
  • Для вызова API Google пользователь должен присутствовать. Приложение использует только токены доступа и не требует токенов обновления.
  • Веб-приложение загружает apis.google.com/js/api.js.
  • Реализация основана на OAuth 2.0 для клиентских веб-приложений.
  • Ваше приложение использует модули gapi.client или gapi.auth2, которые можно найти в клиентской библиотеке Google API для JavaScript.

Признаки того, что в вашем веб-приложении используется обработка кода авторизации:

В некоторых случаях база кода может поддерживать оба процесса.

Выберите процесс авторизации

Прежде чем начать перенос, определите, что лучше всего соответствует вашим потребностям: продолжить использовать существующий поток или перейти на другой.

Чтобы понять основные различия и компромиссы между двумя процессами, ознакомьтесь с разделом Выбор процесса авторизации.

В большинстве случаев рекомендуется использовать процесс авторизации с кодом авторизации, поскольку он обеспечивает наивысший уровень безопасности пользователей. Реализация этого процесса также позволяет вашей платформе добавлять новые офлайн-функции, такие как получение обновлений для уведомления пользователей о заметных изменениях в их календаре, фотографиях и подписках.

Выберите процесс авторизации с помощью селекторов.

Неявное предоставление доступа

Получите токен доступа для использования в браузере, пока пользователь находится в системе.

В разделе Примеры неявного предоставления доступа показаны веб-приложения до и после перехода на сервис идентификации.

Обработка кода авторизации

Код авторизации для каждого пользователя, выданный Google, доставляется на вашу серверную платформу, где он затем обменивается на токен доступа и токен обновления.

Примеры обработки кода авторизации показывают веб-приложения до и после переноса на сервис идентификации.

В этом руководстве следуйте инструкциям, выделенным жирным шрифтом, чтобы добавить, удалить, обновить или заменить существующие функции.

Изменения в веб-приложении для браузера

В этом разделе рассматриваются изменения, которые вы внесете в свое веб-приложение в браузере при переходе на библиотеку JavaScript Сервисов идентификации Google.

Определите затронутый код и протестируйте его

Файл cookie для отладки поможет найти затронутый код и протестировать поведение после прекращения поддержки.

В больших или сложных приложениях может быть трудно найти весь код, затронутый прекращением поддержки модуля gapi.auth2. Чтобы регистрировать в консоли использование функций, которые скоро перестанут поддерживаться, задайте для файла cookie G_AUTH2_MIGRATION значение informational. При необходимости добавьте двоеточие и значение ключа, чтобы также записывать данные в хранилище сеанса. После входа в систему и получения учетных данных просмотрите или отправьте собранные журналы в серверную часть для последующего анализа. Например, informational:showauth2use сохраняет источник и URL в ключе хранилища сеанса с названием showauth2use.

Чтобы проверить поведение приложения, когда модуль gapi.auth2 больше не загружается, задайте для файла cookie G_AUTH2_MIGRATION значение enforced. Это позволяет протестировать поведение после прекращения поддержки до даты вступления в силу.

Возможные значения файлов cookie G_AUTH2_MIGRATION:

  • enforced Не загружайте модуль gapi.auth2.
  • informational Записывать в журнал консоли JS сведения об использовании устаревших функций. Также записывать в хранилище сеанса, если задано необязательное название ключа: informational:key-name.

Чтобы минимизировать влияние на пользователей, рекомендуется сначала установить этот файл cookie локально во время разработки и тестирования, прежде чем использовать его в рабочих средах.

Библиотеки и модули

Модуль gapi.auth2 управляет аутентификацией пользователей для входа и неявным предоставлением доступа для авторизации. Замените этот устаревший модуль, а также его объекты и методы библиотекой Сервисов идентификации Google.

Добавьте библиотеку сервиса идентификации в веб-приложение, включив ее в документ:

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

Удалите все экземпляры загрузки модуля auth2 с помощью gapi.load('auth2', function).

Библиотека Сервисов идентификации Google заменяет модуль gapi.auth2. Вы можете продолжать использовать модуль gapi.client из клиентской библиотеки API Google для JavaScript, а также его функции автоматического создания вызываемых методов JS из документа обнаружения, пакетной обработки нескольких вызовов API и управления CORS.

Файлы cookie

Для авторизации пользователя не требуется использовать файлы cookie.

Подробную информацию о том, как файлы cookie используются для аутентификации пользователей, можно найти в статье Переход с Google Sign-In, а о том, как они используются в других продуктах и сервисах Google, – в статье Как Google использует файлы cookie.

Учетные данные

В сервисах идентификации Google аутентификация и авторизация пользователей разделены на две отдельные операции, а учетные данные пользователей разделены: токен идентификатора, используемый для идентификации пользователя, возвращается отдельно от токена доступа, используемого для авторизации.

Чтобы посмотреть эти изменения, ознакомьтесь с примером учетных данных.

Неявное предоставление доступа

Разделите аутентификацию и авторизацию пользователей, удалив обработку профилей пользователей из процессов авторизации.

Удалите следующие ссылки на клиент JavaScript для входа с аккаунтом Google:

Методы

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

Обработка кода авторизации

Сервис идентификации разделяет учетные данные в браузере на токен идентификатора и токен доступа. Это изменение не относится к учетным данным, полученным в результате прямых вызовов конечных точек Google OAuth 2.0 с вашей серверной платформы или через библиотеки, работающие на защищенном сервере на вашей платформе, например клиент Node.js для API Google.

Состояние сеанса

Ранее вход с аккаунтом Google помогал управлять статусом входа пользователя с помощью:

Вы отвечаете за управление статусом входа и сеансами пользователей в веб-приложении.

Удалите следующие ссылки на клиент JavaScript для входа с аккаунтом Google:

Объекты:

  • gapi.auth2.SignInOptions

Методы

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

Конфигурация клиента

Обновите веб-приложение, чтобы инициализировать клиент токенов для неявной обработки или обработки кода авторизации.

Удалите следующие ссылки на клиент JavaScript для входа с аккаунтом Google:

Объекты:

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

Методы

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

Неявное предоставление доступа

Добавьте объект TokenClientConfig и вызов initTokenClient(), чтобы настроить веб-приложение, как показано в примере Инициализация клиента токена.

Замените ссылки на клиент JavaScript для входа с аккаунтом Google на Сервисы идентификации Google.

Объекты:

  • gapi.auth2.AuthorizeConfig по программе TokenClientConfig

Методы

  • gapi.auth2.init() по программе google.accounts.oauth2.initTokenClient()

Параметры:

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

Обработка кода авторизации

Добавьте объект CodeClientConfig и вызов initCodeClient(), чтобы настроить веб-приложение, как показано в примере Инициализация клиента кода.

При переходе с неявного потока на поток кода авторизации:

Удалите ссылки на клиент JavaScript для входа с аккаунтом Google.

Объекты:

  • gapi.auth2.AuthorizeConfig

Методы

  • gapi.auth2.init()

Параметры:

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

Запрос токена

Жест пользователя, например нажатие кнопки, генерирует запрос, в результате которого токен доступа возвращается непосредственно в браузер пользователя с неявным предоставлением доступа или на вашу серверную платформу после обмена кода авторизации для каждого пользователя на токен доступа и токен обновления.

Неявное предоставление доступа

Токены доступа можно получить и использовать в браузере, пока пользователь вошел в аккаунт и у него есть активный сеанс в Google. В неявном режиме для запроса токена доступа требуется жест пользователя, даже если запрос уже выполнялся ранее.

Замените ссылки на клиент JavaScript для входа с аккаунтом Google на Сервисы идентификации Google.

Методы

  • gapi.auth2.authorize() по программе TokenClient.requestAccessToken()
  • GoogleUser.reloadAuthResponse() с TokenClient.requestAccessToken()

Добавьте ссылку или кнопку для вызова requestAccessToken(), чтобы инициировать всплывающее окно UX для запроса токена доступа или получения нового токена по истечении срока действия существующего.

Обновите базу кода, чтобы:

  • Запустите процесс получения токена OAuth 2.0 с помощью requestAccessToken().
  • Поддерживайте пошаговую авторизацию, используя requestAccessToken и OverridableTokenClientConfig, чтобы разделить один запрос для нескольких областей на несколько меньших запросов.
  • Запросите новый токен, когда срок действия существующего истечет или он будет отозван.

Работа с несколькими областями действия может потребовать структурных изменений в вашей базе кода, чтобы запрашивать доступ к областям действия только по мере необходимости, а не ко всем сразу. Это называется инкрементной авторизацией. Каждый запрос должен содержать как можно меньше областей, в идеале – одну. Подробнее о том, как обрабатывать согласие пользователя, см. в инструкциях по обновлению приложения для дополнительной авторизации.

Когда срок действия токена доступа истекает, модуль gapi.auth2 автоматически получает новый действительный токен для вашего веб-приложения. В целях повышения безопасности пользователей этот процесс автоматического обновления токенов не поддерживается библиотекой сервисов идентификации Google. Ваше веб-приложение должно быть обновлено, чтобы обнаруживать истекший токен доступа и запрашивать новый. Подробную информацию вы найдете в разделе "Обработка токенов".

Обработка кода авторизации

Добавьте ссылку или кнопку, которая будет вызывать requestCode() для запроса кода авторизации у Google. Пример приведен в разделе Запуск процесса авторизации OAuth 2.0.

Подробнее о том, как реагировать на истекший или отозванный токен доступа, читайте в разделе "Обработка токенов".

Обработка токенов

Добавьте обработку ошибок, чтобы обнаруживать неудачные вызовы API Google, когда используется токен доступа с истекшим сроком действия или отозванный токен доступа, и запрашивать новый действительный токен доступа.

Если используется токен доступа с истекшим сроком действия или отозванный токен, API Google возвращают код статуса HTTP 401 Unauthorized и сообщение об ошибке invalid_token. Пример приведен в разделе Ответ с недействительным токеном.

Токены с истекшим сроком действия

Токены доступа действуют недолго, часто всего несколько минут.

Отзыв токена

Владелец аккаунта Google может в любой момент отозвать ранее предоставленное согласие. При этом существующие токены доступа и обновления станут недействительными. Отзыв можно инициировать на вашей платформе с помощью revoke() или через аккаунт Google.

Замените ссылки на клиент JavaScript для входа с аккаунтом Google на Сервисы идентификации Google.

Методы

  • getAuthInstance().disconnect() по программе google.accounts.oauth2.revoke()
  • GoogleUser.disconnect() по программе google.accounts.oauth2.revoke()

Вызывайте revoke, когда пользователь удаляет свой аккаунт на вашей платформе или хочет отозвать согласие на передачу данных вашему приложению.

Когда ваше веб-приложение или серверная платформа запрашивает токен доступа, Google показывает пользователю диалоговое окно для получения согласия. Посмотрите примеры диалоговых окон для получения согласия пользователей, которые Google показывает пользователям.

Прежде чем выдать токен доступа вашему приложению, необходимо запросить согласие пользователя и записать результат в существующем и активном сеансе Google. Если сеанс не был установлен, пользователю может потребоваться войти в аккаунт Google.

Вход пользователей

Пользователи могут войти в аккаунт Google в отдельной вкладке браузера или через браузер или операционную систему. Рекомендуем добавить на сайт кнопку Войти с аккаунтом Google, чтобы при первом открытии приложения устанавливался активный сеанс между аккаунтом Google и браузером. Это дает следующие преимущества:

  • Минимизирует количество входов в аккаунт. Если активного сеанса нет, запрос токена доступа инициирует процесс входа в аккаунт Google.
  • Прямое использование поля credential email токена идентификатора JWT в качестве значения параметра login_hint в объектах CodeClientConfig или TokenClientConfig. Это особенно полезно, если на вашей платформе нет системы управления аккаунтами пользователей.
  • Находить аккаунты Google и связывать их с существующими локальными аккаунтами пользователей на вашей платформе, чтобы минимизировать количество дублирующихся аккаунтов.
  • При создании нового локального аккаунта диалоговые окна и процесс регистрации можно четко отделить от диалоговых окон и процессов аутентификации пользователей, что сократит количество необходимых шагов и снизит процент отказов.

После входа и до выдачи токена доступа пользователи должны предоставить вашему приложению согласие на запрошенные области действия.

После получения согласия возвращается токен доступа вместе со списком областей действия, одобренных или отклоненных пользователем.

Детализированные разрешения позволяют пользователям одобрять или отклонять отдельные области действия. При запросе доступа к нескольким областям каждая область предоставляется или отклоняется независимо от других областей. В зависимости от выбора пользователя приложение выборочно включает функции, которые зависят от отдельной области действия.

Неявное предоставление доступа

Замените ссылки на клиент JavaScript для входа с аккаунтом Google на Сервисы идентификации Google.

Объекты:

  • gapi.auth2.AuthorizeResponse по программе TokenClient.TokenResponse
  • gapi.auth2.AuthResponse по программе TokenClient.TokenResponse

Методы

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

Удалите ссылки на клиент JavaScript для входа с аккаунтом Google.

Методы

  • GoogleUser.getAuthResponse()

Обновите веб-приложение, добавив hasGrantedAllScopes() и hasGrantedAnyScope(), как показано в примере выборочных разрешений.

Обработка кода авторизации

Обновите или добавьте конечную точку кода авторизации на серверную платформу, следуя инструкциям в разделе Обработка кода авторизации.

Обновите платформу, следуя инструкциям в руководстве Использование модели кода, чтобы проверить запрос и получить токен доступа и токен обновления.

Обновите свою платформу, чтобы выборочно включать или отключать функции и возможности в зависимости от отдельных областей действия, одобренных пользователем. Для этого следуйте инструкциям по пошаговой авторизации и проверке областей доступа, предоставленных пользователем.

Примеры неявного предоставления доступа

Старый способ

Клиентская библиотека GAPI

Пример клиентской библиотеки Google API для JavaScript, работающей в браузере и использующей всплывающее окно для получения согласия пользователя.

Модуль gapi.auth2 автоматически загружается и используется модулем gapi.client.init(), поэтому он скрыт.

<!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

OAuth 2.0 для клиентских веб-приложений, работающих в браузере, с использованием всплывающего диалогового окна для получения согласия пользователя.

Модуль gapi.auth2 загружается вручную.

<!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

OAuth 2.0 для клиентских веб-приложений, работающих в браузере, с перенаправлением на сервер авторизации Google для получения согласия пользователя.

В этом примере показаны прямые вызовы конечных точек OAuth 2.0 Google из браузера пользователя без использования модуля gapi.auth2 или библиотеки JavaScript.

<!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>

Новый способ

Только GIS

В этом примере показана только библиотека JavaScript Сервисов идентификации Google, использующая модель токенов и всплывающее окно для согласия пользователя. Она приведена, чтобы показать минимальное количество шагов, необходимых для настройки клиента, запроса и получения токена доступа, а также вызова API Google.

<!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

В этом примере показано, как добавить библиотеку сервисов идентификации Google, используя модель токенов, удалить модуль gapi.auth2 и вызвать API с помощью клиентской библиотеки API Google для JavaScript.

Объекты Promise, async и await используются для принудительного соблюдения порядка загрузки библиотек, а также для выявления и повторной обработки ошибок авторизации. Вызов API выполняется только после того, как будет доступен действительный токен доступа.

Пользователи должны нажать кнопку "Показать календарь", если токен доступа отсутствует при первой загрузке страницы или если срок его действия истек.

<!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

В этом примере показано, как добавить библиотеку сервисов идентификации Google, используя модель токенов, удалить модуль gapi.auth2 и вызвать API с помощью клиентской библиотеки API Google для JavaScript.

Переменные используются для принудительного соблюдения порядка загрузки библиотек. Вызовы GAPI выполняются из обратного вызова после возврата действительного токена доступа.

Пользователи должны нажать кнопку "Показать календарь" при первой загрузке страницы и снова, когда они захотят обновить информацию в календаре.

<!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>

Примеры обработки кода авторизации

Всплывающее окно библиотеки Сервисов идентификации Google может использовать переадресацию URL, чтобы возвращать код авторизации непосредственно в конечную точку токена вашего сервера, или обработчик обратных вызовов JavaScript, работающий в браузере пользователя и передающий ответ на вашу платформу. В любом случае ваша серверная платформа завершит процесс OAuth 2.0, чтобы получить действительный токен обновления и токен доступа.

Старый способ

Веб-приложения на стороне сервера

Вход с аккаунтом Google для серверных приложений, работающих на серверной платформе, с перенаправлением в Google для получения согласия пользователя.

<!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 с переадресацией

Использование OAuth 2.0 для веб-серверных приложений для отправки кода авторизации из браузера пользователя на вашу серверную платформу. Согласие пользователя обрабатывается путем перенаправления браузера пользователя на сервер авторизации Google.

/*
 * 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();
}

Новый способ

Всплывающее окно ГИС UX

В этом примере показана только библиотека JavaScript Сервисов идентификации Google, использующая модель кода авторизации, всплывающее окно для согласия пользователя и обработчик обратных вызовов для получения кода авторизации от Google. Он приведен в качестве примера минимального количества шагов, необходимых для настройки клиента, получения согласия и отправки кода авторизации на вашу серверную платформу.

<!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

Модель кода авторизации поддерживает всплывающие окна и перенаправление в режимах UX для отправки кода авторизации для каждого пользователя на конечную точку, размещенную на вашей платформе. Ниже показан режим UX-переадресации.

<!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

Сервисы идентификации Google – это единая библиотека JavaScript, которая используется для аутентификации и авторизации пользователей. Она объединяет и заменяет функции и возможности, которые ранее были доступны в нескольких разных библиотеках и модулях:

Что нужно сделать при переходе на сервис идентификации:

Существующая библиотека JS Новая библиотека JS Примечания
apis.google.com/js/api.js accounts.google.com/gsi/client Добавьте новую библиотеку и следуйте инструкциям по неявному предоставлению доступа.
apis.google.com/js/client.js accounts.google.com/gsi/client Добавьте новую библиотеку и следуйте инструкциям по обработке кода авторизации.

Краткое руководство по библиотеке

Сравнение объектов и методов старой клиентской библиотеки JavaScript для входа с аккаунтом Google и новой библиотеки Сервисов идентификации Google, а также примечания с дополнительной информацией и действиями, которые нужно выполнить при переносе.

Старая версия Новое Примечания
Объект GoogleAuth и связанные методы:
GoogleAuth.attachClickHandler() Удалить
GoogleAuth.currentUser.get() Удалить
GoogleAuth.currentUser.listen() Удалить
GoogleAuth.disconnect() google.accounts.oauth2.revoke Замените старые данные новыми. Отменить доступ также можно на странице https://myaccount.google.com/permissions.
GoogleAuth.grantOfflineAccess() Удалите, следуйте инструкциям по обработке кода авторизации.
GoogleAuth.isSignedIn.get() Удалить
GoogleAuth.isSignedIn.listen() Удалить
GoogleAuth.signIn() Удалить
GoogleAuth.signOut() Удалить
GoogleAuth.then() Удалить
Объект GoogleUser и связанные методы:
GoogleUser.disconnect() google.accounts.id.revoke Замените старые данные новыми. Отменить доступ также можно на странице https://myaccount.google.com/permissions.
GoogleUser.getAuthResponse() requestCode() или requestAccessToken() Заменить старые значения новыми
GoogleUser.getBasicProfile() Удалить. Вместо этого используйте токен идентификатора. Подробнее о том, как перейти с функции "Войти с аккаунтом Google"…
GoogleUser.getGrantedScopes() hasGrantedAnyScope() Заменить старые значения новыми
GoogleUser.getHostedDomain() Удалить
GoogleUser.getId() Удалить
GoogleUser.grantOfflineAccess() Удалите, следуйте инструкциям по обработке кода авторизации.
GoogleUser.grant() Удалить
GoogleUser.hasGrantedScopes() hasGrantedAnyScope() Заменить старые значения новыми
GoogleUser.isSignedIn() Удалить
GoogleUser.reloadAuthResponse() requestAccessToken() Удалите старый токен доступа и вызовите новый, чтобы заменить токен доступа с истекшим сроком действия или отозванный токен доступа.
Объект gapi.auth2 и связанные методы:
Объект gapi.auth2.AuthorizeConfig TokenClientConfig или CodeClientConfig Заменить старые значения новыми
Объект gapi.auth2.AuthorizeResponse Удалить
Объект gapi.auth2.AuthResponse Удалить
gapi.auth2.authorize() requestCode() или requestAccessToken() Заменить старые значения новыми
gapi.auth2.ClientConfig() TokenClientConfig или CodeClientConfig Заменить старые значения новыми
gapi.auth2.getAuthInstance() Удалить
gapi.auth2.init() initTokenClient() или initCodeClient() Заменить старые значения новыми
Объект gapi.auth2.OfflineAccessOptions Удалить
Объект gapi.auth2.SignInOptions Удалить
Объект gapi.signin2 и связанные методы:
gapi.signin2.render() Удалить. Загрузка элемента g_id_signin в HTML DOM или вызов google.accounts.id.renderButton в JS запускает вход пользователя в аккаунт Google.

Примеры учетных данных

Существующие учетные данные

Библиотека платформы Google Sign-In, клиентская библиотека Google API для JavaScript и прямые вызовы конечных точек Google OAuth 2.0 возвращают в одном ответе и токен доступа OAuth 2.0, и токен идентификатора OpenID Connect.

Пример ответа, содержащего и access_token, и 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

Библиотека Сервисов идентификации Google возвращает:

  • токен доступа, если он используется для авторизации:

    {
      "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"
    }
    
  • или токен идентификации, если он используется для аутентификации:

    {
      "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"
    }
    

Недопустимый ответ токена

Пример ответа от Google при попытке выполнить запрос к API с использованием токена доступа, срок действия которого истек, который был отозван или является недействительным:

Заголовки HTTP-ответов

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

Тело ответа

  {
    "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"
    }
  }