Conectar o complemento do Google Workspace a um serviço de terceiros

Um card de autorização personalizado de uma prévia de link que inclui o logotipo da
  empresa, uma descrição e um botão de login.

Se o complemento do Google Workspace se conectar a um serviço ou API de terceiros que exige autorização, ele poderá solicitar que os usuários façam login e autorizem o acesso.

Esta página explica como autenticar usuários usando um fluxo de autorização (como o OAuth), que inclui as seguintes etapas:

  1. Detectar quando a autorização é necessária.
  2. Retorne uma interface de cartão que solicite que os usuários façam login no serviço.
  3. Atualize o complemento para que os usuários possam acessar o serviço ou recurso protegido.

Se o complemento exigir apenas a identidade do usuário, você poderá autenticar os usuários diretamente usando o ID do Google Workspace ou o endereço de e-mail deles. Para usar o endereço de e-mail para autenticação, consulte Como validar solicitações JSON. Se você criou seu complemento usando o Google Apps Script, pode facilitar esse processo usando a biblioteca OAuth2 para Apps Script (também há uma versão do OAuth1).

Detectar que a autorização é necessária

Ao usar o complemento, os usuários podem não ter autorização para acessar um recurso protegido por vários motivos, como os seguintes:

  • Um token de acesso para se conectar ao serviço de terceiros ainda não foi gerado ou expirou.
  • O token de acesso não abrange o recurso solicitado.
  • O token de acesso não abrange os escopos necessários da solicitação.

Seu complemento precisa detectar esses casos para que os usuários possam fazer login e acessar seu serviço.

Se você estiver criando no Apps Script, a função hasAccess() da biblioteca OAuth poderá informar se você tem acesso a um serviço. Como alternativa, ao usar solicitações UrlFetchApp fetch(), é possível definir o parâmetro muteHttpExceptions como true. Isso impede que a solicitação gere uma exceção em caso de falha e permite que você examine o código e o conteúdo da resposta da solicitação no objeto HttpResponse retornado.

Solicitar que os usuários façam login no seu serviço

Quando o complemento detecta que a autorização é necessária, ele precisa retornar uma interface de card para solicitar que os usuários façam login no serviço. O cartão de login precisa redirecionar os usuários para concluir o processo de autenticação e autorização de terceiros na sua infraestrutura.

Ao criar o complemento usando endpoints HTTP, recomendamos que você proteja o app de destino com o Login do Google e receba o ID do usuário usando o token de identidade emitido durante o login. A subdeclaração contém o ID exclusivo do usuário e pode ser correlacionada com o ID do seu complemento.

Criar e retornar um card de login

Para o cartão de login do seu serviço, use o cartão de autorização básico do Google ou personalize um cartão para exibir informações adicionais, como o logotipo da sua organização. Se você estiver publicando seu complemento, use um card personalizado.

Cartão de autorização básico

A imagem a seguir mostra um exemplo do cartão de autorização básico do Google:

Solicitação de autorização básica para a conta de exemplo. O
    comando diz que o complemento quer mostrar
    mais informações, mas precisa da aprovação do usuário para
    acessar a conta.

O código abaixo mostra um exemplo de uso do cartão de autorização básico do Google:

Apps Script

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

JSON

Retorne a seguinte resposta JSON:

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

Substitua:

  • AUTHORIZATION_URL: o URL do app da Web que processa a autorização.
  • RESOURCE_DISPLAY_NAME: o nome de exibição do recurso ou serviço protegido. Esse nome é exibido para o usuário na solicitacao de autorização. Por exemplo, se o RESOURCE_DISPLAY_NAME for Example Account, o aviso vai dizer "Este complemento quer mostrar mais informações, mas é necessário aprovar o acesso à sua conta Exemplo".

Depois de concluir a autorização, o usuário precisa atualizar o complemento para acessar o recurso protegido.

Cartão de autorização personalizado

Para modificar a solicitação de autorização, crie um card personalizado para a experiência de login do serviço.

Se você estiver publicando seu complemento, use um cartão de autorização personalizado. Para saber mais sobre os requisitos de publicação do Google Workspace Marketplace, consulte Sobre a análise de apps.

O cartão retornado precisa fazer o seguinte:

  • Deixe claro para o usuário que o complemento está solicitando permissão para acessar um serviço que não é do Google em nome dele.
  • Deixe claro o que o complemento pode fazer se autorizado.
  • Conter um botão ou widget semelhante que leve o usuário ao URL de autorização do serviço. Verifique se a função do widget é óbvia para o usuário.
  • O widget acima precisa usar a configuração OnClose.RELOAD no objeto OpenLink para garantir que o complemento seja recarregado após a autorização.
  • Todos os links abertos na solicitação de autorização precisam usar HTTPS.

A imagem a seguir mostra um exemplo de card de autorização personalizada para a página inicial de um complemento. O card inclui um logotipo, uma descrição e um botão de login:

Um cartão de autorização personalizado para a Cymbal Labs que inclui o logotipo,
  uma descrição e um botão de login da empresa.

O código a seguir mostra como usar este exemplo de card personalizado:

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

Retorne a seguinte resposta 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"
                    }
                  }
                ]
              }
            ]
          }
        }
      ]
    }
  }
}

Substitua:

  • LOGO_URL: o URL de um logotipo ou imagem. Precisa ser um URL público.
  • LOGO_ALT_TEXT: texto alternativo para o logotipo ou a imagem, como Cymbal Labs Logo.
  • DESCRIPTION: um call-to-action para os usuários fazerem login, como Sign in to get started.
  • Para atualizar o botão de login:
    • AUTHORIZATION_URL: o URL do app da Web que processa a autorização.
    • Opcional: para mudar a cor do botão, atualize os valores de ponto flutuante RGBA do campo color. Para o Apps Script, atualize o método setBackgroundColor() usando valores hexadecimais.
  • TEXT_SIGN_UP: um texto que solicita que os usuários criem uma conta, caso não tenham uma. Por exemplo, New to Cymbal Labs? <a href=\"https://www.example.com/signup\">Sign up</a> here.

Gerenciar logins de terceiros nos apps do Google Workspace

Um uso comum dos complementos do Google Workspace é fornecer uma interface para interagir com um sistema de terceiros em um aplicativo host do Google Workspace.

Os sistemas de terceiros geralmente exigem que o usuário faça login usando um ID de usuário, uma senha ou outra credencial. Quando um usuário faz login no serviço de terceiros enquanto usa um host do Google Workspace, é necessário garantir que ele não precise fazer login novamente ao mudar para outro host do Google Workspace.

Se você estiver criando no Apps Script, poderá impedir solicitações de login repetidas com propriedades de usuário ou tokens de ID. Eles são explicados nas seções a seguir.

Propriedades do usuário

É possível armazenar os dados de login de um usuário nas propriedades do usuário do Apps Script. Por exemplo, você pode criar seu próprio JSON Web Token (JWT) do serviço de login e registrar isso em uma propriedade do usuário ou registrar o nome de usuário e a senha do serviço.

As propriedades do usuário têm um escopo que permite que elas sejam acessadas apenas por esse usuário no script do seu complemento. Outros usuários e outros scripts não podem acessar essas propriedades. Veja PropertiesService para mais detalhes.

Tokens de ID

Você pode usar um token de ID do Google como a credencial de login do seu serviço. Essa é uma maneira de fazer o logon único. Os usuários já fizeram login no Google porque estão em um app host do Google.

Exemplo de configuração de OAuth que não é do Google

O exemplo de código do Apps Script a seguir mostra como configurar um complemento para usar uma API que não é do Google que exige o OAuth. Este exemplo usa a biblioteca OAuth2 para Apps Script para criar um serviço de acesso à API.

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