Verifica las solicitudes de Google Chat

En el caso de las apps de Google Chat creadas en extremos HTTP, en esta sección, se explica cómo verificar que las solicitudes a tu extremo provengan de Chat.

Para enviar eventos de interacción al extremo de tu app de Chat, Google realiza solicitudes HTTPS a tu servicio. Para verificar que la solicitud proviene de Google, Chat incluye un token de ID de OpenID Connect (OIDC) firmado por Google como un token de portador en el encabezado Authorization de cada solicitud HTTPS (y en el campo authorizationEventObject.systemIdToken del cuerpo de la solicitud). Por ejemplo:

POST
Host: yourappurl.com
Authorization: Bearer AbCdEf123456
Content-Type: application/json
User-Agent: Google-Dynamite

La cadena AbCdEf123456 en el ejemplo anterior es el token de autorización de portador. Este token criptográfico está firmado por la cuenta de servicio única por proyecto de tu app de Chat (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com), y el campo audience se establece en la URL del extremo HTTP configurada para tu app de Chat cuando configuras la app de Chat.

Puedes copiar la dirección de correo electrónico de la cuenta de servicio de tu app de Chat en la sección Configuración de la conexión de la pestaña Configuración de la API de Chat en la consola de Google Cloud:

  1. En la consola de Google Cloud, ve a Menú > APIs y servicios > APIs y servicios habilitados > API de Google Chat > Configuración:

    Ir a la configuración de la API de Google Chat

  2. En Funciones interactivas > Configuración de conexión, copia el correo electrónico de la cuenta de servicio (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com).

Si implementaste tu app de Chat con funciones de Cloud Run o Cloud Run, Cloud IAM controla la verificación de tokens automáticamente cuando le otorgas a la cuenta de servicio de la app de Chat el rol Cloud Run Invoker (roles/run.invoker). Si tu app implementa su propio servidor HTTP, puedes verificar el token de portador con una biblioteca cliente de la API de Google de código abierto:

Si el token no se verifica para la app de Chat, tu servicio debe responder a la solicitud con un código de respuesta HTTPS 401 (Unauthorized).

Autentica solicitudes con Cloud Run Functions

Si la lógica de tu función se implementa con Cloud Run Functions o Cloud Run, asegúrate de que las URLs de los extremos HTTP configuradas en Activadores en la configuración de conexión de la app de Chat correspondan a la URL del extremo de tu función de Cloud Run.

Luego, autoriza la cuenta de servicio de tu app de Chat (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com, copiada de la sección Configuración de conexión de la pestaña Configuración de la API de Chat) como invocador siguiendo estos pasos:

Console

Después de implementar tu función o servicio en Google Cloud, haz lo siguiente:

  1. En la consola de Google Cloud, ve a la página Cloud Run:

    Ir a Cloud Run

  2. En la lista de servicios de Cloud Run, haz clic en la casilla de verificación junto a la función receptora. (No hagas clic en la función).

  3. Haz clic en Permisos en la parte superior de la pantalla. Se abrirá el panel Permisos.

  4. Haz clic en Agregar principal.

  5. En el campo Principales nuevas, ingresa la dirección de correo electrónico de la cuenta de servicio de tu app de Chat (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com).

  6. En el menú Selecciona un rol, elige el rol Cloud Run.

    Invocador de Cloud Run

  7. Haz clic en Guardar.

gcloud

Usa el comando gcloud functions add-invoker-policy-binding:

gcloud functions add-invoker-policy-binding RECEIVING_FUNCTION \
  --member='serviceAccount:service-PROJECT_NUMBER@gcp-sa-gsuiteaddons.iam.gserviceaccount.com'

Reemplaza lo siguiente:

  • RECEIVING_FUNCTION: Es el nombre de la función de tu app de Chat.
  • PROJECT_NUMBER: Es el número de proyecto de la dirección de correo electrónico de la cuenta de servicio de tu app de Chat.

Autentica solicitudes HTTP con un token de ID

En el caso de los extremos HTTP, el token de autorización de portador en la solicitud es un token de ID de OpenID Connect (OIDC) firmado por Google. El campo email se establece en la dirección de correo electrónico de la cuenta de servicio de tu app de Chat (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com), y el campo audience se establece en la URL del extremo HTTP configurada para recibir la solicitud. Por ejemplo, si el extremo configurado de tu app de Chat es https://example.com/app/, el campo audience en el token de ID es https://example.com/app/.

Este es el método de autenticación recomendado si tu extremo HTTP no está alojado en un servicio que admita la autenticación basada en IAM (como Cloud Run).

En los siguientes ejemplos, se muestra cómo verificar que Google emitió el token del portador para tu app de Chat y que se dirige al extremo de tu app con la biblioteca cliente de OAuth de Google:

Java

java/chat/secured-app/src/main/java/com/google/chat/app/secured/App.java
/**
 * Determine whether a Google Workspace add-on request is legitimate.
 * 
 * @param event Event sent from Google Workspace add-on
 * @param authorization Authorization header from the request
 * @return {boolean} Whether the request is legitimate
 */
private boolean verifyAddOnRequest(JsonNode event, String authorization) throws Exception {
  JsonFactory factory = JacksonFactory.getDefaultInstance();

  GoogleIdTokenVerifier verifier =
    new GoogleIdTokenVerifier.Builder(new ApacheHttpTransport(), factory)
      .setAudience(Collections.singletonList(HTTP_ENDPOINT))
      .build();

  String bearer = authorization.substring("Bearer ".length(), authorization.length());
  GoogleIdToken idToken = GoogleIdToken.parse(factory, bearer);
  return idToken != null
    && verifier.verify(idToken)
    && idToken.getPayload().getEmailVerified()
    && idToken.getPayload().getEmail().equals(SERVICE_ACCOUNT_EMAIL);
}

Python

python/chat/secured-app/main.py
def verifyAddOnRequest() -> bool:
  """Determine whether a Google Workspace add-on request is legitimate.

  Args:
    request: Request sent from Google Workspace add-on

  Returns:
    Whether the request is legitimate
  """
  try:
    bearer = request.headers.get('Authorization')[len("Bearer "):]
    token = id_token.verify_oauth2_token(bearer, requests.Request(), HTTP_ENDPOINT)
    return token['email'] == SERVICE_ACCOUNT_EMAIL

  except:
    return False

Node.js

node/chat/secured-app/index.js
/**
 * Determine whether a Google Workspace add-on request is legitimate.
 * 
 * @param {Object} req Request sent from Google Workspace add-on
 * @return {boolean} Whether the request is legitimate
 */
async function verifyAddOnRequest(req) {
  try {
    const authorization = req.headers.authorization;
    const idToken = authorization.substring('Bearer '.length, authorization.length);
    const ticket = await new OAuth2Client().verifyIdToken({idToken, audience: HTTP_ENDPOINT});
    return ticket.getPayload().email_verified
        && ticket.getPayload().email === SERVICE_ACCOUNT_EMAIL;
  } catch (unused) {
    return false;
  }
}

Apps de Chat que no son complementos: Verifica las solicitudes de Google Chat

La siguiente documentación se aplica a las apps de Chat que no son complementos de Google Workspace. Para migrar una app de Chat que no sea un complemento, consulta Cómo convertir una app de Google Chat en un complemento de Google Workspace.

En el caso de las apps de Chat que no son complementos configurados con URL del extremo HTTP en Configuración de conexión, el tipo del token de portador y el valor del campo audience dependen del tipo de Público de autenticación que seleccionaste cuando configuraste la app de Chat, y las solicitudes se firman con la cuenta de servicio compartida chat@system.gserviceaccount.com.

Autentica solicitudes con Cloud Run Functions (apps de chat que no son complementos)

Si la lógica de tu función se implementa con Cloud Run Functions, debes seleccionar URL del extremo HTTP en el campo Público de autenticación del parámetro de configuración de conexión de la app de Chat y asegurarte de que la URL del extremo HTTP en la configuración corresponda a la URL del extremo de Cloud Run Function.

Luego, debes autorizar la cuenta de servicio de Google Chat chat@system.gserviceaccount.com como invocador siguiendo estos pasos:

Console

Después de implementar tu función o servicio en Google Cloud, haz lo siguiente:

  1. En la consola de Google Cloud, ve a la página Cloud Run:

    Ir a Cloud Run

  2. En la lista de servicios de Cloud Run, haz clic en la casilla de verificación junto a la función receptora. (No hagas clic en la función).

  3. Haz clic en Permisos en la parte superior de la pantalla. Se abrirá el panel Permisos.

  4. Haz clic en Agregar principal.

  5. En el campo Principales nuevas, ingresa chat@system.gserviceaccount.com.

  6. En el menú Selecciona un rol, elige el rol Cloud Run.

    Invocador de Cloud Run

  7. Haz clic en Guardar.

gcloud

Usa el comando gcloud functions add-invoker-policy-binding:

gcloud functions add-invoker-policy-binding RECEIVING_FUNCTION \
  --member='serviceAccount:chat@system.gserviceaccount.com'

Reemplaza RECEIVING_FUNCTION por el nombre de la función de tu app de Chat.

Autentica solicitudes HTTP con un token de ID (apps de Chat que no son complementos)

Si el campo Authentication Audience del parámetro de configuración de conexión de la app de Chat que no es un complemento está establecido en URL del extremo HTTP, el token de autorización de portador en la solicitud es un token de ID de OpenID Connect (OIDC) firmado por Google. El campo email está configurado como chat@system.gserviceaccount.com. El campo Público de autenticación está configurado con la URL a la que configuraste Google Chat para que envíe solicitudes a tu app de Chat que no es un complemento. Por ejemplo, si el extremo configurado de tu app de Chat es https://example.com/app/, el campo Público de autenticación en el token de ID es https://example.com/app/.

En los siguientes ejemplos, se muestra cómo verificar que Google Chat emitió el token de portador y que está dirigido a tu app de Chat que no es un complemento con la biblioteca cliente de Google OAuth.

Java

java/basic-app/src/main/java/com/google/chat/app/basic/App.java
String CHAT_ISSUER = "chat@system.gserviceaccount.com";
JsonFactory factory = JacksonFactory.getDefaultInstance();

GoogleIdTokenVerifier verifier =
    new GoogleIdTokenVerifier.Builder(new ApacheHttpTransport(), factory)
        .setAudience(Collections.singletonList(AUDIENCE))
        .build();

GoogleIdToken idToken = GoogleIdToken.parse(factory, bearer);
return idToken != null
    && verifier.verify(idToken)
    && idToken.getPayload().getEmailVerified()
    && idToken.getPayload().getEmail().equals(CHAT_ISSUER);

Python

python/basic-app/main.py
# Bearer Tokens received by apps will always specify this issuer.
CHAT_ISSUER = 'chat@system.gserviceaccount.com'

try:
    # Verify valid token, signed by CHAT_ISSUER, intended for a third party.
    request = requests.Request()
    token = id_token.verify_oauth2_token(bearer, request, AUDIENCE)
    return token['email'] == CHAT_ISSUER

except:
    return False

Node.js

node/basic-app/index.js
// Bearer Tokens received by apps will always specify this issuer.
const chatIssuer = 'chat@system.gserviceaccount.com';

// Verify valid token, signed by chatIssuer, intended for a third party.
try {
  const ticket = await client.verifyIdToken({
    idToken: bearer,
    audience: audience
  });
  return ticket.getPayload().email_verified
      && ticket.getPayload().email === chatIssuer;
} catch (unused) {
  return false;
}

Autentica solicitudes con un JWT de número de proyecto (apps de chat que no son complementos)

Si el campo Authentication Audience del parámetro de configuración de conexión de la app de Chat que no es un complemento está establecido en Project Number, el token de autorización de portador en la solicitud es un token web JSON (JWT) autofirmado, emitido y firmado por chat@system.gserviceaccount.com. El campo audience se establece en el número del proyecto de Google Cloud que usaste para compilar tu app de Chat que no es un complemento. Por ejemplo, si el número de tu proyecto de Cloud de la app de Chat es 1234567890, el campo audience del JWT será 1234567890.

En los siguientes ejemplos, se muestra cómo verificar que Google Chat haya emitido el token de portador y que esté dirigido a tu proyecto con la biblioteca cliente de Google OAuth.

Java

java/basic-app/src/main/java/com/google/chat/app/basic/App.java
String CHAT_ISSUER = "chat@system.gserviceaccount.com";
JsonFactory factory = JacksonFactory.getDefaultInstance();

GooglePublicKeysManager keyManagerBuilder =
    new GooglePublicKeysManager.Builder(new ApacheHttpTransport(), factory)
        .setPublicCertsEncodedUrl(
            "https://www.googleapis.com/service_accounts/v1/metadata/x509/" + CHAT_ISSUER)
        .build();

GoogleIdTokenVerifier verifier =
    new GoogleIdTokenVerifier.Builder(keyManagerBuilder).setIssuer(CHAT_ISSUER).build();

GoogleIdToken idToken = GoogleIdToken.parse(factory, bearer);
return idToken != null
    && verifier.verify(idToken)
    && idToken.verifyAudience(Collections.singletonList(AUDIENCE))
    && idToken.verifyIssuer(CHAT_ISSUER);

Python

python/basic-app/main.py
# Bearer Tokens received by apps will always specify this issuer.
CHAT_ISSUER = 'chat@system.gserviceaccount.com'

try:
    # Verify valid token, signed by CHAT_ISSUER, intended for a third party.
    request = requests.Request()
    certs_url = 'https://www.googleapis.com/service_accounts/v1/metadata/x509/' + CHAT_ISSUER
    token = id_token.verify_token(bearer, request, AUDIENCE, certs_url)
    return token['iss'] == CHAT_ISSUER

except:
    return False

Node.js

node/basic-app/index.js
// Bearer Tokens received by apps will always specify this issuer.
const chatIssuer = 'chat@system.gserviceaccount.com';

// Verify valid token, signed by CHAT_ISSUER, intended for a third party.
try {
  const response = await fetch('https://www.googleapis.com/service_accounts/v1/metadata/x509/' + chatIssuer);
  const certs = await response.json();
  await client.verifySignedJwtWithCertsAsync(
    bearer, certs, audience, [chatIssuer]);
  return true;
} catch (unused) {
  return false;
}