Conecta tu complemento de Google Workspace a un servicio de terceros

Una tarjeta de autorización personalizada de una vista previa del vínculo que incluye el logotipo de la empresa, una descripción y un botón de acceso.
Una interfaz de tarjeta de acceso para un complemento que muestra vistas previas de vínculos de un servicio de terceros.

Si tu complemento de Google Workspace se conecta a un servicio o API de terceros que requiere autorización, el complemento puede solicitar a los usuarios que accedan y autoricen el acceso.

En esta página, se explica cómo autenticar usuarios con un flujo de autorización (como OAuth), que incluye los siguientes pasos:

  1. Detectar cuándo se requiere autorización
  2. Devuelve una interfaz de tarjeta que solicita a los usuarios que accedan al servicio.
  3. Actualiza el complemento para que los usuarios puedan acceder al servicio o al recurso protegido.

Si tu complemento solo requiere la identidad del usuario, puedes autenticar a los usuarios directamente con su ID o dirección de correo electrónico de Google Workspace. Para usar la dirección de correo electrónico para la autenticación, consulta cómo validar solicitudes JSON. Si creaste tu complemento con Google Apps Script, puedes facilitar este proceso con la biblioteca de OAuth2 para Google Apps Script (también hay una versión de OAuth1).

Detecta que se requiere autorización

Cuando usan tu complemento, es posible que los usuarios no estén autorizados para acceder a un recurso protegido por varios motivos, como los siguientes:

  • Aún no se generó un token de acceso para conectarse al servicio de terceros o este expiró.
  • El token de acceso no cubre el recurso solicitado.
  • El token de acceso no cubre los permisos requeridos de la solicitud.

Tu complemento debe detectar estos casos para que los usuarios puedan acceder y usar tu servicio.

Si compilas en Apps Script, la función hasAccess() de la biblioteca de OAuth puede indicarte si tienes acceso a un servicio. Como alternativa, cuando uses solicitudes de UrlFetchApp fetch(), puedes establecer el parámetro muteHttpExceptions en true. Esto evita que la solicitud genere una excepción en caso de falla y te permite examinar el código y el contenido de la respuesta en el objeto HttpResponse devuelto.

Solicitar a los usuarios que accedan a tu servicio

Cuando tu complemento detecta que se requiere autorización, debe devolver una interfaz de tarjeta para solicitar a los usuarios que accedan al servicio. La tarjeta de acceso debe redireccionar a los usuarios para que completen el proceso de autenticación y autorización de terceros en tu infraestructura.

Cuando crees tu complemento con endpoints HTTP, te recomendamos que protejas la app de destino con el Acceso con Google y que obtengas el ID del usuario con el token de identidad que se emite durante el acceso. La subreclamación contiene el ID único del usuario y se puede correlacionar con el ID de tu complemento.

Compila y devuelve una tarjeta de acceso

Para la tarjeta de acceso de tu servicio, puedes usar la tarjeta de autorización básica de Google o personalizar una tarjeta para mostrar información adicional, como el logotipo de tu organización. Si publicas tu complemento de forma pública, debes usar una tarjeta personalizada.

Tarjeta de autorización básica

En la siguiente imagen, se muestra un ejemplo de la tarjeta de autorización básica de Google:

Es el mensaje básico de autorización para la cuenta de ejemplo. El mensaje indica que el complemento desea mostrar información adicional, pero necesita la aprobación del usuario para acceder a la cuenta.

Para mostrar a los usuarios una tarjeta de autorización básica, debes devolver el objeto AuthorizationError. En el siguiente código, se muestra un ejemplo de un objeto AuthorizationError:

Apps Script

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

JSON

Devuelve la siguiente respuesta JSON:

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

Reemplaza lo siguiente:

  • AUTHORIZATION_URL: Es la URL de la app web que controla la autorización.
  • RESOURCE_DISPLAY_NAME: Es el nombre visible del recurso o servicio protegido. Este nombre se muestra al usuario en el mensaje de autorización. Por ejemplo, si tu RESOURCE_DISPLAY_NAME es Example Account, el mensaje dirá "Este complemento desea mostrar información adicional, pero necesita aprobación para acceder a tu cuenta de ejemplo".

Después de completar la autorización, se le solicita al usuario que actualice el complemento para acceder al recurso protegido.

Devuelve tarjetas de autorización en Google Chat

Si tu complemento extiende Google Chat y el usuario lo ejecuta dentro de Google Chat, el usuario puede completar el proceso de autorización sin una actualización manual. Google Chat admite el reintento automático de la ejecución anterior si el activador es Mensaje, Se agregó al espacio o Comando de la app. En el caso de estos activadores, tu complemento recibe completeRedirectUri en la carga útil del evento. Debes codificar completeRedirectUri en la URL de configuración para activar el reintento automático. El redireccionamiento a esta URL le indica a Google Chat que se cumplió con la solicitud de configuración y permite que Google Chat vuelva a intentar la ejecución anterior.

Cuando se redirige correctamente a un usuario al configCompleteRedirectUrl proporcionado en el mensaje original, Google Chat realiza los siguientes pasos:

  1. Borra la instrucción que se muestra al usuario que inicia la acción.
  2. Envía el objeto del evento original al mismo complemento por segunda vez.

Si no codificas completeRedirectUri en la URL de configuración, el usuario podrá completar el flujo de autorización. Sin embargo, Google Chat no vuelve a intentar la ejecución anterior, y el usuario debe invocar manualmente tu complemento de nuevo.

En el siguiente ejemplo de código, se muestra cómo una app de Chat puede solicitar credenciales de OAuth2 sin conexión, almacenarlas en una base de datos y usarlas para realizar llamadas a la API con autenticación de usuarios.

Tarjeta de autorización personalizada

Para modificar el mensaje de autorización, puedes crear una tarjeta personalizada para la experiencia de acceso de tu servicio.

Si publicas tu complemento de forma pública, debes usar una tarjeta de autorización personalizada para todas las aplicaciones host de Google Workspace, excepto Chat. Para obtener más información sobre los requisitos de publicación de Google Workspace Marketplace, consulta Acerca de la revisión de apps.

La tarjeta devuelta debe hacer lo siguiente:

  • Indícale claramente al usuario que el complemento le solicita permiso para acceder a un servicio que no es de Google en su nombre.
  • Deja en claro qué puede hacer el complemento si se autoriza.
  • Contener un botón o un widget similar que dirija al usuario a la URL de autorización del servicio Asegúrate de que la función de este widget sea obvia para el usuario.
  • El widget anterior debe usar el parámetro de configuración OnClose.RELOAD en su objeto OpenLink para garantizar que el complemento se vuelva a cargar después de que se reciba la autorización.
  • Todos los vínculos que se abran desde el mensaje de autorización deben usar HTTPS.

En la siguiente imagen, se muestra un ejemplo de una tarjeta de autorización personalizada para la página principal de un complemento. La tarjeta incluye un logotipo, una descripción y un botón de acceso:

Una tarjeta de autorización personalizada para Cymbal Labs que incluye el logotipo de la empresa, una descripción y un botón de acceso.

En el siguiente código, se muestra cómo usar este ejemplo de tarjeta personalizada:

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

Devuelve la siguiente respuesta 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"
                    }
                  }
                ]
              }
            ]
          }
        }
      ]
    }
  }
}

Reemplaza lo siguiente:

  • LOGO_URL: URL de un logotipo o una imagen. Debe ser una URL pública.
  • LOGO_ALT_TEXT: Es el texto alternativo del logotipo o la imagen, como Cymbal Labs Logo.
  • DESCRIPTION: Es un llamado a la acción para que los usuarios accedan, como Sign in to get started.
  • Para actualizar el botón de acceso, haz lo siguiente:
    • AUTHORIZATION_URL: Es la URL de la app web que controla la autorización.
    • Opcional: Para cambiar el color del botón, actualiza los valores de coma flotante RGBA del campo color. En Apps Script, actualiza el método setBackgroundColor() con valores hexadecimales.
  • TEXT_SIGN_UP: Es un texto que les solicita a los usuarios que creen una cuenta si no tienen una. Por ejemplo, New to Cymbal Labs? <a href=\"https://www.example.com/signup\">Sign up</a> here

Administra los accesos de terceros en las apps de Google Workspace

Una aplicación común para los complementos de Google Workspace es proporcionar una interfaz para interactuar con un sistema de terceros desde una aplicación host de Google Workspace.

Los sistemas de terceros suelen requerir que el usuario acceda con un ID de usuario, una contraseña o alguna otra credencial. Cuando un usuario accede a tu servicio de terceros mientras usa un host de Google Workspace, debes asegurarte de que no tenga que volver a acceder cuando cambie a otro host de Google Workspace.

Si compilas en Apps Script, puedes evitar las solicitudes de acceso repetidas con propiedades del usuario o tokens de ID. Estos se explican en las siguientes secciones.

Propiedades del usuario

Puedes almacenar los datos de acceso de un usuario en las propiedades del usuario de Apps Script. Por ejemplo, podrías crear tu propio token web JSON (JWT) desde su servicio de acceso y registrarlo en una propiedad del usuario, o bien registrar el nombre de usuario y la contraseña de su servicio.

Las propiedades del usuario se definen de tal manera que solo son accesibles para ese usuario dentro de la secuencia de comandos de tu complemento. Otros usuarios y otros secuencias de comandos no pueden acceder a estas propiedades. Consulta PropertiesService para obtener más detalles.

Tokens de ID

Puedes usar un token de ID de Google como credencial de acceso para tu servicio. Esta es una forma de lograr el inicio de sesión único. Los usuarios ya accedieron a Google porque se encuentran en una app host de Google.

Ejemplo de configuración de OAuth que no es de Google

En el siguiente ejemplo de código de Apps Script, se muestra cómo configurar un complemento para usar una API que no es de Google y que requiere OAuth. En este ejemplo, se usa la biblioteca de OAuth2 para Apps Script para crear un servicio que permita acceder a la 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();
}