Verifica las solicitudes de Google Chat

En el caso de las apps de Google Chat compiladas 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 a tu servicio. Para verificar que la solicitud provenga de Google, Chat incluye un token del portador en el encabezado Authorization de cada solicitud HTTPS que se envía a tu extremo. Por ejemplo:

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

La string AbCdEf123456 del ejemplo anterior es el token de autorización del portador. Este es un token criptográfico producido por Google. El tipo del token del portador y el valor del campo audience dependen del tipo de público de autenticación que seleccionaste cuando configuraste la app de Chat.

Si implementaste tu app de Chat mediante Cloud Functions o Cloud Run, Cloud IAM maneja la verificación de tokens automáticamente. Solo necesitas agregar la cuenta de servicio de Google Chat como invocador autorizado. Si tu app implementa su propio servidor HTTP, puedes verificar el token del portador mediante 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 Functions o Cloud Run

Si la lógica de tu función se implementa mediante Cloud Functions o Cloud Run, debes seleccionar App URL en el campo Authentication Audience de la configuración de conexión de la app de Chat y asegurarte de que la URL de la app en la configuración corresponda a la URL del extremo de Cloud Function o Cloud Run.

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

En los siguientes pasos, se muestra cómo usar Cloud Functions (1a gen.):

Console

Después de implementar la función en Google Cloud, sigue estos pasos:

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

    Ir a Cloud Functions

  2. En la lista de Cloud Functions, 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. Selecciona la función Cloud Functions > Invocador de Cloud Functions en el menú desplegable Selecciona una función.

  7. Haz clic en Guardar.

gcloud

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

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

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

En los siguientes pasos, se muestra cómo usar los servicios de Cloud Functions (2a gen.) o de Cloud Run:

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 de 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. Selecciona el rol Cloud Run > Invocador de Cloud Run en el menú desplegable Selecciona un rol.

  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 con un token de ID de URL de la app

Si el campo de público de autenticación de la configuración de conexión de la app de Chat se establece en App URL, el token de autorización del portador en la solicitud es un token de ID de OpenID Connect (OIDC) firmado por Google. El campo email se establece en chat@system.gserviceaccount.com. El campo audience se establece en la URL que configuraste en Google Chat para enviar solicitudes a tu app de Chat. Por ejemplo, si el extremo configurado de la app de Chat es https://example.com/app/, el campo audience 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 del portador y que se segmentó a tu app mediante la biblioteca cliente de Google OAuth.

Java

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collections;

import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.googleapis.auth.oauth2.GooglePublicKeysManager;
import com.google.api.client.http.apache.ApacheHttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.json.JsonFactory;

/** Tool for verifying JWT Tokens for Apps in Google Chat. */
public class JWTVerify {
  // Bearer Tokens received by apps will always specify this issuer.
  static String CHAT_ISSUER = "chat@system.gserviceaccount.com";

  // Intended audience of the token, which is the URL of the app.
  static String AUDIENCE = "https://example.com/app/";

  // Get this value from the request's Authorization HTTPS header.
  // For example, for "Authorization: Bearer AbCdEf123456" use "AbCdEf123456".
  static String BEARER_TOKEN = "AbCdEf123456";

  public static void main(String[] args) throws GeneralSecurityException, IOException {
    JsonFactory factory = new GsonFactory();

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

    GoogleIdToken idToken = GoogleIdToken.parse(factory, BEARER_TOKEN);
    if (idToken == null) {
      System.out.println("Token cannot be parsed");
      System.exit(-1);
    }

    // Verify valid token, signed by CHAT_ISSUER, intended for a third party.
    if (!verifier.verify(idToken)
        || !idToken.getPayload().getEmailVerified()
        || !idToken.getPayload().getEmail().equals(CHAT_ISSUER)) {
      System.out.println("Invalid token");
      System.exit(-1);
    }

    // Token originates from Google and is targeted to a specific client.
    System.out.println("The token is valid");
  }
}

Python

import sys
from google.oauth2 import id_token
from google.auth.transport import requests

# Bearer Tokens received by apps will always specify this issuer.
CHAT_ISSUER = 'chat@system.gserviceaccount.com'

# Intended audience of the token, which is the URL of the app.
AUDIENCE = 'https://example.com/app/'

# Get this value from the request's Authorization HTTPS header.
# For example, for 'Authorization: Bearer AbCdEf123456' use 'AbCdEf123456'.
BEARER_TOKEN = 'AbCdEf123456'

try:
  # Verify valid token, signed by CHAT_ISSUER, intended for a third party.
  request = requests.Request()
  token = id_token.verify_oauth2_token(BEARER_TOKEN, request, AUDIENCE)

  if token['email'] != CHAT_ISSUER:
    sys.exit('Invalid token')
except:
  sys.exit('Invalid token')

# Token originates from Google and is targeted to a specific client.
print('The token is valid')

Node.js

import {OAuth2Client} from 'google-auth-library';

// Bearer Tokens received by apps will always specify this issuer.
const CHAT_ISSUER = 'chat@system.gserviceaccount.com';

// Intended audience of the token, which is the URL of the app.
const AUDIENCE = 'https://example.com/app/';

// Get this value from the request's Authorization HTTPS header.
// For example, for "Authorization: Bearer AbCdEf123456" use "AbCdEf123456"
const BEARER_TOKEN = 'AbCdEf123456';

const client = new OAuth2Client();

async function verify() {
  // Verify valid token, signed by CHAT_ISSUER, intended for a third party.
  try {
    const ticket = await client.verifyIdToken({
      idToken: BEARER_TOKEN,
      audience: AUDIENCE
    });
    if (!ticket.getPayload().email_verified
        || ticket.getPayload().email !== CHAT_ISSUER) {
      throw new Error('Invalid issuer');
    }
  } catch (unused) {
    console.error('Invalid token');
    process.exit(1);
  }

  // Token originates from Google and is targeted to a specific client.
  console.log('The token is valid');
}

verify();

Autentica solicitudes con un JWT de número de proyecto

Si el campo de público de autenticación de la configuración de conexión de la app de Chat se establece en Project Number (o no se establece), el token de autorización del 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 de proyecto de Google Cloud que usaste para compilar la app de Chat. Por ejemplo, si el número del proyecto de Cloud de la app de Chat es 1234567890, el campo audience en el JWT será 1234567890.

En los siguientes ejemplos, se muestra cómo verificar que Google Chat emitió el token del portador y que se segmentó a tu proyecto con la biblioteca cliente de OAuth de Google.

Java

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collections;

import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.googleapis.auth.oauth2.GooglePublicKeysManager;
import com.google.api.client.http.apache.ApacheHttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.json.JsonFactory;

/** Tool for verifying JWT Tokens for Apps in Google Chat. */
public class JWTVerify {
  // Bearer Tokens received by apps will always specify this issuer.
  static String CHAT_ISSUER = "chat@system.gserviceaccount.com";

  // Url to obtain the public certificate for the issuer.
  static String PUBLIC_CERT_URL_PREFIX =
      "https://www.googleapis.com/service_accounts/v1/metadata/x509/";

  // Intended audience of the token, which is the project number of the app.
  static String AUDIENCE = "1234567890";

  // Get this value from the request's Authorization HTTPS header.
  // For example, for "Authorization: Bearer AbCdEf123456" use "AbCdEf123456".
  static String BEARER_TOKEN = "AbCdEf123456";

  public static void main(String[] args) throws GeneralSecurityException, IOException {
    JsonFactory factory = new GsonFactory();

    GooglePublicKeysManager.Builder keyManagerBuilder =
        new GooglePublicKeysManager.Builder(new ApacheHttpTransport(), factory);

    String certUrl = PUBLIC_CERT_URL_PREFIX + CHAT_ISSUER;
    keyManagerBuilder.setPublicCertsEncodedUrl(certUrl);

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

    GoogleIdToken idToken = GoogleIdToken.parse(factory, BEARER_TOKEN);
    if (idToken == null) {
      System.out.println("Token cannot be parsed");
      System.exit(-1);
    }

    // Verify valid token, signed by CHAT_ISSUER, intended for a third party.
    if (!verifier.verify(idToken)
        || !idToken.verifyAudience(Collections.singletonList(AUDIENCE))
        || !idToken.verifyIssuer(CHAT_ISSUER)) {
      System.out.println("Invalid token");
      System.exit(-1);
    }

    // Token originates from Google and is targeted to a specific client.
    System.out.println("The token is valid");
  }
}

Python

import sys

from google.oauth2 import id_token
from google.auth.transport import requests

# Bearer Tokens received by apps will always specify this issuer.
CHAT_ISSUER = 'chat@system.gserviceaccount.com'

# Url to obtain the public certificate for the issuer.
PUBLIC_CERT_URL_PREFIX = 'https://www.googleapis.com/service_accounts/v1/metadata/x509/'

# Intended audience of the token, which will be the project number of the app.
AUDIENCE = '1234567890'

# Get this value from the request's Authorization HTTPS header.
# For example, for 'Authorization: Bearer AbCdEf123456' use 'AbCdEf123456'.
BEARER_TOKEN = 'AbCdEf123456'

try:
  # Verify valid token, signed by CHAT_ISSUER, intended for a third party.
  request = requests.Request()
  certs_url = PUBLIC_CERT_URL_PREFIX + CHAT_ISSUER
  token = id_token.verify_token(BEARER_TOKEN, request, AUDIENCE, certs_url)

  if token['iss'] != CHAT_ISSUER:
    sys.exit('Invalid issuer')
except:
  sys.exit('Invalid token')

# Token originates from Google and is targeted to a specific client.
print('The token is valid')

Node.js

import fetch from 'node-fetch';
import {OAuth2Client} from 'google-auth-library';

// Bearer Tokens received by apps will always specify this issuer.
const CHAT_ISSUER = 'chat@system.gserviceaccount.com';

// Url to obtain the public certificate for the issuer.
const PUBLIC_CERT_URL_PREFIX =
    'https://www.googleapis.com/service_accounts/v1/metadata/x509/';

// Intended audience of the token, which is the project number of the app.
const AUDIENCE = '1234567890';

// Get this value from the request's Authorization HTTPS header.
// For example, for "Authorization: Bearer AbCdEf123456" use "AbCdEf123456"
const BEARER_TOKEN = 'AbCdEf123456';

const client = new OAuth2Client();

/** Verifies JWT Tokens for Apps in Google Chat. */
async function verify() {
  // Verify valid token, signed by CHAT_ISSUER, intended for a third party.
  try {
    const response = await fetch(PUBLIC_CERT_URL_PREFIX + CHAT_ISSUER);
    const certs = await response.json();
    const ticket = await client.verifySignedJwtWithCertsAsync(
        BEARER_TOKEN, certs, AUDIENCE, [CHAT_ISSUER]);
  } catch (unused) {
    console.error('Invalid token');
    process.exit(1);
  }

  // Token originates from Google and is targeted to a specific client.
  console.log('The token is valid');
}

verify();