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, uma descrição e um botão de login da empresa.
Uma interface de card de login para um complemento que mostra links de um serviço de terceiros.

Se o complemento do Google Workspace se conectar a um serviço ou API de terceiros que exija 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. Retorna uma interface de card que pede aos usuários para fazer 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 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 validação de solicitações JSON. Se você criou seu 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 seu 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.

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 pode informar se você tem acesso a um serviço. Como alternativa, ao usar solicitações UrlFetchApp fetch(), defina o parâmetro muteHttpExceptions como true. Isso evita que a solicitação gere uma exceção em caso de falha e permite examinar o código e o conteúdo da resposta no objeto HttpResponse retornado.

Pedir 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 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 seu 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 declaração "sub" 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 card de login do seu serviço, você pode usar o card de autorização básica do Google ou personalizar um card para mostrar mais informações, como o logotipo da sua organização. Se você estiver publicando seu complemento publicamente, use um card personalizado.

Card de autorização básica

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

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

Para mostrar aos usuários um card de autorização básica, retorne o objeto AuthorizationError. O código a seguir mostra um exemplo de 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 é exibido ao usuário na solicitação de autorização. Por exemplo, se seu RESOURCE_DISPLAY_NAME for Example Account, a solicitação vai dizer: "Este complemento quer mostrar mais informações, mas é necessário autorizar o acesso à sua Conta de exemplo".

Depois de concluir a autorização, o usuário vai receber uma solicitação para atualizar o complemento e acessar o recurso protegido.

Cartões de autorização de devolução no Google Chat

Se o complemento estender o Google Chat e o usuário executar o complemento 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 gatilho for Mensagem, Adicionado ao espaço ou Comando do app. Para esses gatilhos, o 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 indica 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 realiza as seguintes etapas:

  1. Apaga o comando mostrado ao usuário que iniciou a ação.
  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 executar novamente a execução anterior, e o usuário precisa invocar manualmente o complemento de novo.

O exemplo de código a seguir mostra como um app de 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 de usuário.

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 seu serviço.

Se você estiver publicando seu complemento publicamente, use um cartão 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:

  • Deixe 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.
  • Deixe claro o que o complemento pode fazer se autorizado.
  • Ter 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 está ó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 depois que a autorização for recebida.
  • 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 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: uma 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. No Apps Script, atualize o método setBackgroundColor() 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 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 com um ID de usuário, uma senha ou outra credencial. Quando um usuário faz login no seu serviço de terceiros enquanto usa um host do Google Workspace, é preciso garantir que ele não precise fazer login de novo 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. Elas são explicadas 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 ele no script do complemento. Outros usuários e 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á 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 exige OAuth. Esta amostra 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();
}