Łączenie dodatku do Google Workspace z usługą innej firmy

Element autoryzujący z logo firmy, opisem i przyciskiem logowania wyświetlany w podglądzie linku.
Karta logowania w dodatku, która wyświetla podgląd linków z usługi innej firmy.

Jeśli Twój dodatek do Google Workspace łączy się z usługą lub interfejsem API innej firmy, która wymaga autoryzacji, może wyświetlić użytkownikom prośbę o zalogowanie się i autoryzację dostępu.

Na tej stronie znajdziesz informacje o uwierzytelnianiu użytkowników za pomocą procesu autoryzacji (np. OAuth), który obejmuje te czynności:

  1. wykrywać, kiedy wymagana jest autoryzacja;
  2. Zwraca interfejs karty, który prosi użytkowników o zalogowanie się w usłudze.
  3. Odśwież dodatek, aby użytkownicy mogli uzyskać dostęp do usługi lub chronionego zasobu.

Jeśli Twój dodatek wymaga tylko tożsamości użytkownika, możesz uwierzytelnić użytkowników bezpośrednio, korzystając z ich identyfikatorów Google Workspace lub adresów e-mail. Aby używać adresu e-mail do uwierzytelniania, zapoznaj się z artykułem Walidowanie żądań JSON. Jeśli dodatek został utworzony za pomocą Google Apps Script, możesz ułatwić ten proces, korzystając z biblioteki OAuth2 dla Google Apps Script (dostępna jest też wersja OAuth1).

wykrywanie, że wymagana jest autoryzacja;

Podczas korzystania z Twojego dodatku użytkownicy mogą nie mieć uprawnień do dostępu do zasobu chronionego z różnych powodów, takich jak:

  • Token dostępu do usługi innej firmy nie został jeszcze wygenerowany lub wygasł.
  • Token dostępu nie obejmuje żądanego zasobu.
  • Token dostępu nie obejmuje wymaganych zakresów żądania.

Twój dodatek powinien wykrywać takie przypadki, aby użytkownicy mogli się zalogować i uzyskać dostęp do Twojej usługi.

Jeśli tworzysz aplikację w Apps Script, funkcja biblioteki OAuth hasAccess() może Ci pomóc sprawdzić, czy masz dostęp do usługi. Możesz też w żądaniach UrlFetchApp fetch() ustawić parametr muteHttpExceptions na true. Zapobiega to zgłaszaniu wyjątku przez żądanie w przypadku jego niepowodzenia i umożliwia sprawdzenie kodu odpowiedzi żądania oraz treści w zwróconym obiekcie HttpResponse.

Zachęcaj użytkowników do zalogowania się w usłudze

Gdy dodatek wykryje, że wymagane jest uwierzytelnianie, musi zwrócić interfejs karty, aby zachęcić użytkowników do zalogowania się w usłudze. Karta logowania musi przekierowywać użytkowników do procesu uwierzytelniania i autoryzacji w Twojej infrastrukturze.

Podczas tworzenia dodatku za pomocą punktów końcowych HTTP zalecamy zabezpieczenie aplikacji docelowej za pomocą logowania przez Google oraz uzyskanie identyfikatora użytkownika za pomocą tokenu tożsamości wydanego podczas logowania. Subclaim zawiera unikalny identyfikator użytkownika i może być powiązany z identyfikatorem z Twojego dodatku.

Tworzenie i zwracanie karty logowania

W przypadku karty logowania do usługi możesz użyć podstawowej karty autoryzacyjnej Google lub dostosować kartę, aby wyświetlać dodatkowe informacje, takie jak logo organizacji. Jeśli publikujesz dodatek publicznie, musisz użyć karty niestandardowej.

Podstawowa karta upoważnienia

Na poniższym obrazku widać przykład karty autoryzacyjnej Google:

Podstawowy komunikat autoryzujący dla konta Example Pojawia się wtedy komunikat, że dodatek chce wyświetlić dodatkowe informacje, ale potrzebuje zgody użytkownika na dostęp do konta.

Poniższy kod pokazuje przykład użycia podstawowej karty autoryzacji Google:

Google Apps Script

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

JSON

Zwraca odpowiedź JSON o tym kształcie:

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

Zastąp następujące elementy:

  • AUTHORIZATION_URL: adres URL aplikacji internetowej, która obsługuje autoryzację.
  • RESOURCE_DISPLAY_NAME: wyświetlana nazwa chronionego zasobu lub usługi. Ta nazwa jest wyświetlana użytkownikowi w prośbie o autoryzację. Jeśli na przykład RESOURCE_DISPLAY_NAME to Example Account, pojawi się komunikat „Ten dodatek chce wyświetlić dodatkowe informacje, ale w tym celu potrzebuje zgody na dostęp do Twojego konta Example”.

Po zakończeniu autoryzacji użytkownik otrzyma prośbę o odświeżenie dodatku, aby uzyskać dostęp do zasobu chronionego.

Karta autoryzacji niestandardowej

Aby zmodyfikować prośbę o autoryzację, możesz utworzyć kartę niestandardową na potrzeby logowania w usłudze.

Jeśli publikujesz dodatek publicznie, musisz użyć niestandardowej karty autoryzacyjnej. Więcej informacji o wymaganiach dotyczących publikowania aplikacji w Google Workspace Marketplace znajdziesz w artykule Sprawdzanie aplikacji.

Zwrócona karta musi:

  • wyraźnie poinformować użytkownika, że dodatek prosi o dostęp do usługi innej niż Google w jego imieniu.
  • Jasno określ, co dodatek może zrobić, jeśli zostanie autoryzowany.
  • zawierać przycisk lub podobny widżet, który przekierowuje użytkownika do adresu URL autoryzacji usługi; Upewnij się, że funkcja widżetu jest oczywista dla użytkownika.
  • Widżet musi używać ustawienia OnClose.RELOAD w obiekcie OpenLink, aby po otrzymaniu autoryzacji ponownie wczytywać dodatek.
  • Wszystkie linki otwierane z prośby o autoryzację muszą używać protokołu HTTPS.

Na ilustracji poniżej widać przykładową kartę autoryzacji niestandardowej na stronie głównej dodatku. Karta zawiera logo, opis i przycisk logowania:

Niestandardowa karta autoryzacyjna Cymbal Labs z logo firmy, opisem i przyciskiem logowania.

Poniższy kod pokazuje, jak używać tej przykładowej karty niestandardowej:

Google 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

Zwraca odpowiedź JSON o tym kształcie:

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

Zastąp następujące elementy:

  • LOGO_URL: adres URL logo lub obrazu. Musi to być publiczny adres URL.
  • LOGO_ALT_TEXT: tekst alternatywny logo lub obrazu, np. Cymbal Labs Logo.
  • DESCRIPTION: wezwanie do działania, aby użytkownicy mogli się zalogować, na przykład Sign in to get started.
  • Aby zaktualizować przycisk logowania:
    • AUTHORIZATION_URL: adres URL aplikacji internetowej, która obsługuje autoryzację.
    • Opcjonalnie: aby zmienić kolor przycisku, zaktualizuj wartości typu float w polu color w formacie RGBA. W przypadku Apps Script zaktualizuj metodę setBackgroundColor(), używając wartości szesnastkowych.
  • TEXT_SIGN_UP: tekst zachęcający użytkowników do utworzenia konta, jeśli go nie mają. Na przykład: New to Cymbal Labs? <a href=\"https://www.example.com/signup\">Sign up</a> here.

Zarządzanie logowaniem się w aplikacjach Google Workspace za pomocą kont innych firm

Jednym z typowych zastosowań dodatków do Google Workspace jest udostępnianie interfejsu do interakcji z systemem innej firmy z poziomu aplikacji hosta Google Workspace.

Systemy innych firm często wymagają, aby użytkownik zalogował się, podając identyfikator użytkownika, hasło lub inne dane logowania. Gdy użytkownik loguje się w usłudze innej firmy, korzystając z jednego hosta Google Workspace, musisz się upewnić, że nie musi ponownie logować się, gdy przełączy się na innego hosta Google Workspace.

Jeśli tworzysz aplikację w Apps Script, możesz uniemożliwić powtarzające się żądania logowania za pomocą właściwości użytkownika lub tokenów identyfikacyjnych. W następnych sekcjach omawiamy te opcje.

Właściwości użytkownika

Dane logowania użytkownika możesz przechowywać w właściwościach użytkownika w Apps Script. Możesz na przykład utworzyć własny token sieciowy JSON (JWT) z usługi logowania i zapisać go w właściwości użytkownika lub zapisać nazwę użytkownika i hasło do tej usługi.

Właściwości użytkownika są ograniczone tak, aby były dostępne tylko dla tego użytkownika w skrypcie dodatku. Inni użytkownicy i inne skrypty nie mają dostępu do tych właściwości. Więcej informacji znajdziesz w sekcji PropertiesService.

tokeny identyfikatora;

Jako danych logowania do usługi możesz użyć tokenu Google ID. Jest to sposób na logowanie jednokrotne. Użytkownicy są już zalogowani w Google, ponieważ korzystają z aplikacji hostowanej przez Google.

Przykład konfiguracji OAuth innej niż Google

Poniższy przykładowy kod Apps Script pokazuje, jak skonfigurować dodatek do korzystania z interfejsu API spoza Google, który wymaga uwierzytelniania OAuth. Ten przykład korzysta z biblioteki OAuth2 dla Apps Script do tworzenia usługi dostępu do interfejsu API.

Google 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. 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();
}