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

Um cartão 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.
Uma interface de card de login para um complemento que mostra a visualização de links de um serviço de terceiros.

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

Nesta página, explicamos 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. Retornar uma interface de card que pede aos usuários para fazer login no serviço.
  3. Atualizar o complemento para que os usuários possam acessar o serviço ou o recurso protegido.

Se o complemento exigir apenas a identidade do usuário, você poderá autenticar os usuários diretamente usando o ID ou o endereço de e-mail do Google Workspace. Para usar o endereço de e-mail na autenticação, consulte Validar solicitações JSON. Se você criou o complemento usando o Google Apps Script, pode facilitar esse processo usando a biblioteca OAuth2 para Google 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 estar autorizados a acessar um recurso protegido por vários motivos, como:

  • 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.

O 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 UrlFetchApp.fetch solicitações, você pode definir o parâmetro muteHttpExceptions como true. Isso impede que a solicitação gere uma exceção em caso de falha e permite examinar o código de resposta e o conteúdo da solicitação no objeto HttpResponse retornado.

Pedir aos usuários para fazer login no seu serviço

Quando o complemento detecta que a autorização é necessária, ele precisa retornar uma interface de card para pedir que os usuários façam login no serviço. O card 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, nós 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 complemento.

Criar e retornar um card de login

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

Card de autorização básico

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

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

Para pedir aos usuários um card de autorização básico, retorne o objeto AuthorizationError. O código a seguir mostra um exemplo de um objeto AuthorizationError:

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 é mostrado ao usuário na solicitação de autorização. Por exemplo, se o RESOURCE_DISPLAY_NAME for Example Account, a solicitação vai dizer "Este complemento quer mostrar informações adicionais, mas é necessário aprovar o acesso à sua conta de exemplo".

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

Retornar cards de autorização no Google Chat

Se o complemento estender o Google Chat e o usuário o executar no Google Chat, ele poderá concluir o processo de autorização sem uma atualização manual. O Google Chat oferece suporte à nova tentativa automática da execução anterior se o acionador for Mensagem, Adicionado ao espaço ou Comando do app. Para esses acionadores, seu complemento recebe completeRedirectUri no payload do evento. É necessário codificar completeRedirectUri no URL de configuração para acionar a nova tentativa automática. O redirecionamento para esse URL sinaliza ao Google Chat que a solicitação de configuração foi atendida e permite que o Google Chat tente novamente a execução anterior.

Quando um usuário é redirecionado para o configCompleteRedirectUrl fornecido na mensagem original, o Google Chat executa as seguintes etapas:

  1. Apaga a solicitação mostrada ao usuário iniciador.
  2. Envia o objeto de evento original para o mesmo complemento uma segunda vez.

Se você não codificar completeRedirectUri no URL de configuração, o usuário ainda poderá concluir o fluxo de autorização. No entanto, o Google Chat não tenta novamente a execução anterior, e o usuário precisa invocar o complemento manualmente de novo.

O exemplo de código a seguir mostra como um app do Chat pode solicitar credenciais OAuth2 off-line, armazená-las em um banco de dados e usá-las para fazer chamadas de API com autenticação do usuário.

Card de autorização personalizado

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

Se você estiver publicando o complemento publicamente, use um card de autorização personalizado para todos os aplicativos host do Google Workspace, exceto o Chat. Para saber mais sobre os requisitos de publicação do Google Workspace Marketplace, consulte Sobre a análise de apps.

O card retornado precisa fazer o seguinte:

  • Deixar claro para o usuário que o complemento está pedindo permissão para acessar um serviço que não é do Google em nome dele.
  • Deixar 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 desse widget é óbvia para o usuário.
  • O widget anterior precisa usar a configuração OnClose.RELOAD no objeto OpenLink para garantir que o complemento seja recarregado após o recebimento da 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 personalizado 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 esse 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 imagem, como Cymbal Labs Logo.
  • DESCRIPTION: uma chamada para ação para que os usuários façam 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 flutuantes RGBA do color campo. Para o Apps Script, atualize o setBackgroundColor método usando valores hexadecimais.
  • TEXT_SIGN_UP: um texto que pede aos usuários para criar uma conta se eles não tiverem uma. Por exemplo, New to Cymbal Labs? <a href=\"https://www.example.com/signup\">Sign up</a> here.

Gerenciar logins de terceiros em apps do Google Workspace

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

Os sistemas de terceiros geralmente exigem que o usuário faça login usando um ID de usuário, senha ou outra credencial. Quando um usuário faz login no seu serviço de terceiros enquanto usa um host do Google Workspace, você precisa 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á evitar solicitações de login repetidas com propriedades do usuário ou tokens de ID. Esses itens 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) no 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 são definidas de forma que só possam ser acessadas por esse usuário no script do complemento. Outros usuários e outros scripts não podem acessar essas propriedades. Consulte PropertiesService para mais detalhes.

Tokens de ID

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

Exemplo de configuração do 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 e que exige o OAuth. Esse 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.
*/
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();
}