Подключите дополнение Google Workspace к стороннему сервису.

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

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

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

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

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

Обнаружить, что требуется авторизация

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

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

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

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

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

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

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

Создайте и верните карту входа

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

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

На следующем изображении показан пример базовой карты авторизации Google:

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

В следующем коде показан пример использования базовой карты авторизации Google:

Скрипт приложений

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 , в сообщении будет указано: «Это дополнение хотело бы показать дополнительную информацию, но для доступа к вашей примерной учетной записи требуется одобрение».

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

Индивидуальная карта авторизации

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

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

Возвращенная карта должна выполнить следующие действия:

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

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

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

Следующий код показывает, как использовать этот пример пользовательской карты:

Скрипт приложений

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. Например, вы можете создать свой собственный веб-токен JSON (JWT) из их службы входа и записать его в свойстве пользователя или записать имя пользователя и пароль для их службы.

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

ID-токены

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

Пример конфигурации OAuth, отличной от Google

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

Скрипт приложений

/**
* 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. Also sets a timer to close the window
*          automatically.
*/
function authCallback(callbackRequest) {
  var authorized = getOAuthService().handleCallback(callbackRequest);
  if (authorized) {
    return HtmlService.createHtmlOutput(
      'Success! <script>setTimeout(function() { top.window.close() }, 1);</script>');
  } 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();
}