Как подключить дополнение Google Workspace к стороннему сервису

Специальная карточка авторизации из предпросмотра ссылки, на которой есть логотип компании, описание и кнопка входа.
Интерфейс карточки входа для дополнения, которое позволяет просматривать ссылки из стороннего сервиса.

Если дополнение Google Workspace подключается к стороннему сервису или API, для которого требуется авторизация, дополнение может предложить пользователям войти в аккаунт и предоставить доступ.

На этой странице рассказывается, как выполнить аутентификацию пользователей с помощью потока авторизации (например, OAuth). Этот процесс включает следующие этапы:

  1. определять, когда требуется авторизация;
  2. Возвращает интерфейс карточки, в котором пользователям предлагается войти в сервис.
  3. Обновите дополнение, чтобы пользователи могли получить доступ к сервису или защищенному ресурсу.

Если вашему дополнению требуется только идентификатор пользователя, вы можете напрямую аутентифицировать пользователей, используя их идентификатор Google Workspace или адрес электронной почты. Чтобы использовать адрес электронной почты для аутентификации, ознакомьтесь с информацией о том, как проверять запросы JSON. Если вы создали дополнение с помощью Google Apps Script, вы можете упростить этот процесс, используя библиотеку OAuth2 для Google Apps Script (также доступна версия OAuth1).

определять, что требуется авторизация;

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

  • Токен доступа для подключения к стороннему сервису ещё не создан или срок его действия истек.
  • Токен доступа не распространяется на запрошенный ресурс.
  • Токен доступа не охватывает области действия, необходимые для запроса.

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

Если вы используете Apps Script, функция библиотеки OAuth hasAccess может сообщить, есть ли у вас доступ к сервису. Если вы используете запросы UrlFetchApp.fetch, то можете задать для параметра muteHttpExceptions значение true. Это предотвращает возникновение исключения при сбое запроса и позволяет изучить код ответа и контент в возвращенном объекте HttpResponse.

Предлагать пользователям войти в ваш сервис

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

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

Как создать и вернуть карточку для входа

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

Карта базовой авторизации

На изображении ниже показана стандартная карта авторизации Google.

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

Чтобы показать пользователям карточку с базовой авторизацией, необходимо вернуть объект AuthorizationError. Ниже приведен пример объекта AuthorizationError:

Apps Script

CardService.newAuthorizationException()
    .setAuthorizationUrl('AUTHORIZATION_URL')
    .setResourceDisplayName('RESOURCE_DISPLAY_NAME')
    .throwException();

JSON

Верните следующий ответ JSON:

{
  "basic_authorization_prompt": {
    "authorization_url": "AUTHORIZATION_URL",
    "resource": "RESOURCE_DISPLAY_NAME"
  }
}

Замените следующее:

  • AUTHORIZATION_URL – URL веб-приложения, которое обрабатывает авторизацию.
  • RESOURCE_DISPLAY_NAME – отображаемое название защищенного ресурса или сервиса. Это название показывается пользователю в запросе авторизации. Например, если ваш аккаунт RESOURCE_DISPLAY_NAME – Example Account, в запросе будет указано: "Это дополнение может показывать дополнительную информацию, но для этого ему нужно предоставить доступ к аккаунту Example Account".

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

Как возвращать карты авторизации в Google Chat

Если дополнение расширяет возможности Google Chat и пользователь запускает его в Google Chat, он может завершить процесс авторизации без обновления страницы вручную. Google Chat поддерживает автоматическую повторную попытку предыдущего выполнения, если триггер – Сообщение, Добавление в чат-группу или Команда приложения. При срабатывании этих триггеров дополнение получает completeRedirectUri в полезной нагрузке события. Чтобы автоматически повторить попытку, в URL конфигурации нужно закодировать символ completeRedirectUri. Переход на этот URL сообщает Google Chat, что запрос на настройку выполнен, и позволяет Google Chat повторить предыдущее выполнение.

Когда пользователь успешно перенаправляется на configCompleteRedirectUrl, указанный в исходном сообщении, Google Chat выполняет следующие действия:

  1. Удаляет запрос, показанный пользователю, который инициировал действие.
  2. Отправляет исходный объект события в то же дополнение во второй раз.

Если вы не закодируете completeRedirectUri в URL конфигурации, пользователь все равно сможет завершить процесс авторизации. Однако Google Chat не пытается повторно выполнить предыдущую операцию, и пользователь должен вручную запустить дополнение ещё раз.

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

Пользовательская карта авторизации

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

Если вы публикуете дополнение в открытом доступе, для всех размещаемых приложений Google Workspace, кроме Chat, необходимо использовать специальную карточку авторизации. Чтобы узнать больше о требованиях к публикации в Google Workspace Marketplace, ознакомьтесь со статьей О проверке приложений.

Возвращенная карта должна:

  • Пользователь должен понимать, что дополнение запрашивает разрешение на доступ к стороннему сервису от его имени.
  • Четко укажите, что может делать дополнение, если ему предоставить доступ.
  • Содержать кнопку или похожий виджет, который ведет пользователя на URL авторизации сервиса. Убедитесь, что пользователю понятна функция виджета.
  • В предыдущем виджете необходимо использовать параметр OnClose.RELOAD в объекте OpenLink, чтобы дополнение перезагружалось после получения авторизации.
  • Все ссылки, открываемые из запроса на авторизацию, должны использовать протокол HTTPS.

На изображении ниже показан пример пользовательской карточки авторизации на главной странице дополнения. На карточке есть логотип, описание и кнопка входа:

Специальная карта авторизации для Cymbal Labs с логотипом компании, описанием и кнопкой входа.

Ниже приведен код, который можно использовать для создания такой карточки:

Apps Script

function customAuthorizationCard() {
    let cardSection1Image1 = CardService.newImage()
        .setImageUrl('LOGO_URL')
        .setAltText('LOGO_ALT_TEXT');

    let cardSection1Divider1 = CardService.newDivider();

    let cardSection1TextParagraph1 = CardService.newTextParagraph()
        .setText('DESCRIPTION');

    let cardSection1ButtonList1Button1 = CardService.newTextButton()
        .setText('Sign in')
        .setBackgroundColor('#0055ff')
        .setTextButtonStyle(CardService.TextButtonStyle.FILLED)
        .setAuthorizationAction(CardService.newAuthorizationAction()
            .setAuthorizationUrl('AUTHORIZATION_URL'));

    let cardSection1ButtonList1 = CardService.newButtonSet()
        .addButton(cardSection1ButtonList1Button1);

    let cardSection1TextParagraph2 = CardService.newTextParagraph()
        .setText('TEXT_SIGN_UP');

    let cardSection1 = CardService.newCardSection()
        .addWidget(cardSection1Image1)
        .addWidget(cardSection1Divider1)
        .addWidget(cardSection1TextParagraph1)
        .addWidget(cardSection1ButtonList1)
        .addWidget(cardSection1TextParagraph2);

    let card = CardService.newCardBuilder()
        .addSection(cardSection1)
        .build();
    return [card];
}

function startNonGoogleAuth() {
    CardService.newAuthorizationException()
        .setAuthorizationUrl('AUTHORIZATION_URL')
        .setResourceDisplayName('RESOURCE_DISPLAY_NAME')
        .setCustomUiCallback('customAuthorizationCard')
        .throwException();
  }

JSON

Верните следующий ответ JSON:

{
  "custom_authorization_prompt": {
    "action": {
      "navigations": [
        {
          "pushCard": {
            "sections": [
              {
                "widgets": [
                  {
                    "image": {
                      "imageUrl": "LOGO_URL",
                      "altText": "LOGO_ALT_TEXT"
                    }
                  },
                  {
                    "divider": {}
                  },
                  {
                    "textParagraph": {
                      "text": "DESCRIPTION"
                    }
                  },
                  {
                    "buttonList": {
                      "buttons": [
                        {
                          "text": "Sign in",
                          "onClick": {
                            "openLink": {
                              "url": "AUTHORIZATION_URL",
                              "onClose": "RELOAD",
                              "openAs": "OVERLAY"
                            }
                          },
                          "color": {
                            "red": 0,
                            "green": 0,
                            "blue": 1,
                            "alpha": 1,
                          }
                        }
                      ]
                    }
                  },
                  {
                    "textParagraph": {
                      "text": "TEXT_SIGN_UP"
                    }
                  }
                ]
              }
            ]
          }
        }
      ]
    }
  }
}

Замените следующее:

  • LOGO_URL – URL логотипа или изображения. Должен быть общедоступным URL.
  • LOGO_ALT_TEXT – альтернативный текст для логотипа или изображения, например Cymbal Labs Logo.
  • DESCRIPTION – призыв к действию, например Sign in to get started.
  • Чтобы изменить кнопку входа, выполните следующие действия:
    • AUTHORIZATION_URL – URL веб-приложения, которое обрабатывает авторизацию.
    • Чтобы изменить цвет кнопки, обновите значения RGBA в поле color. В Apps Script обновите метод setBackgroundColor, используя шестнадцатеричные значения.
  • TEXT_SIGN_UP – текст, предлагающий пользователям создать аккаунт, если у них его нет. Пример: New to Cymbal Labs? <a href=\"https://www.example.com/signup\">Sign up</a> here.

Как управлять сторонними способами входа в приложения Google Workspace

Одно из распространенных применений дополнений Google Workspace – предоставление интерфейса для взаимодействия со сторонней системой из размещаемого приложения Google Workspace.

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

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

Свойства пользователя

Вы можете хранить данные для входа пользователя в свойствах пользователя Apps Script. Например, вы можете создать собственный токен JWT на основе сервиса входа в систему и записать его в свойство пользователя или сохранить имя пользователя и пароль для сервиса.

Свойства пользователей имеют такую область действия, что они доступны только этому пользователю в скрипте дополнения. Другие пользователи и скрипты не могут получить доступ к этим свойствам. Подробнее: PropertiesService.

токены идентификации;

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

Пример конфигурации OAuth для стороннего сервиса

В приведенном ниже примере кода Apps Script показано, как настроить дополнение для использования API стороннего сервиса, требующего OAuth. В этом примере для создания сервиса, который будет использоваться для доступа к API, применяется библиотека OAuth2 для Apps Script.

Apps Script

/**
* Attempts to access a non-Google API using a constructed service
* object.
*
* If your add-on needs access to non-Google APIs that require OAuth,
* you need to implement this method. You can use the OAuth1 and
* OAuth2 Apps Script libraries to help implement it.
*
* @param {String} url         The URL to access.
* @param {String} method_opt  The HTTP method. Defaults to GET.
* @param {Object} headers_opt The HTTP headers. Defaults to an empty
*                             object. The Authorization field is added
*                             to the headers in this method.
* @return {HttpResponse} the result from the UrlFetchApp.fetch() call.
*/
function accessProtectedResource(url, method_opt, headers_opt) {
  var service = getOAuthService();
  var maybeAuthorized = service.hasAccess();
  if (maybeAuthorized) {
    // A token is present, but it may be expired or invalid. Make a
    // request and check the response code to be sure.

    // Make the UrlFetch request and return the result.
    var accessToken = service.getAccessToken();
    var method = method_opt || 'get';
    var headers = headers_opt || {};
    headers['Authorization'] =
        Utilities.formatString('Bearer %s', accessToken);
    var resp = UrlFetchApp.fetch(url, {
      'headers': headers,
      'method' : method,
      'muteHttpExceptions': true, // Prevents thrown HTTP exceptions.
    });

    var code = resp.getResponseCode();
    if (code >= 200 && code < 300) {
      return resp.getContentText("utf-8"); // Success
    } else if (code == 401 || code == 403) {
      // Not fully authorized for this action.
      maybeAuthorized = false;
    } else {
      // Handle other response codes by logging them and throwing an
      // exception.
      console.error("Backend server error (%s): %s", code.toString(),
                    resp.getContentText("utf-8"));
      throw ("Backend server error: " + code);
    }
  }

  if (!maybeAuthorized) {
    // Invoke the authorization flow using the default authorization
    // prompt card.
    CardService.newAuthorizationException()
        .setAuthorizationUrl(service.getAuthorizationUrl())
        .setResourceDisplayName("Display name to show to the user")
        .throwException();
  }
}

/**
* Create a new OAuth service to facilitate accessing an API.
* This example assumes there is a single service that the add-on needs to
* access. Its name is used when persisting the authorized token, so ensure
* it is unique within the scope of the property store. You must set the
* client secret and client ID, which are obtained when registering your
* add-on with the API.
*
* See the Apps Script OAuth2 Library documentation for more
* information:
*   https://github.com/googlesamples/apps-script-oauth2#1-create-the-oauth2-service
*
*  @return A configured OAuth2 service object.
*/
function getOAuthService() {
  return OAuth2.createService('SERVICE_NAME')
      .setAuthorizationBaseUrl('SERVICE_AUTH_URL')
      .setTokenUrl('SERVICE_AUTH_TOKEN_URL')
      .setClientId('CLIENT_ID')
      .setClientSecret('CLIENT_SECRET')
      .setScope('SERVICE_SCOPE_REQUESTS')
      .setCallbackFunction('authCallback')
      .setCache(CacheService.getUserCache())
      .setPropertyStore(PropertiesService.getUserProperties());
}

/**
* Boilerplate code to determine if a request is authorized and returns
* a corresponding HTML message. When the user completes the OAuth2 flow
* on the service provider's website, this function is invoked from the
* service. In order for authorization to succeed you must make sure that
* the service knows how to call this function by setting the correct
* redirect URL.
*
* The redirect URL to enter is:
* https://script.google.com/macros/d/<Apps Script ID>/usercallback
*
* See the Apps Script OAuth2 Library documentation for more
* information:
*   https://github.com/googlesamples/apps-script-oauth2#1-create-the-oauth2-service
*
*  @param {Object} callbackRequest The request data received from the
*                  callback function. Pass it to the service's
*                  handleCallback() method to complete the
*                  authorization process.
*  @return {HtmlOutput} a success or denied HTML message to display to
*          the user.
*/
function authCallback(callbackRequest) {
  var authorized = getOAuthService().handleCallback(callbackRequest);
  if (authorized) {
    return HtmlService.createHtmlOutput(
      'Success!');
  } else {
    return HtmlService.createHtmlOutput('Denied');
  }
}

/**
* Unauthorizes the non-Google service. This is useful for OAuth
* development/testing.  Run this method (Run > resetOAuth in the script
* editor) to reset OAuth to re-prompt the user for OAuth.
*/
function resetOAuth() {
  getOAuthService().reset();
}