Valider les requêtes provenant de Google Chat

Pour les applications Google Chat basées sur des points de terminaison HTTP, cette section explique comment vérifier que les requêtes adressées à votre point de terminaison proviennent de Chat.

Pour envoyer des événements d'interaction au point de terminaison de votre application Chat, Google envoie des requêtes à votre service. Pour vérifier que la requête provient de Google, Chat inclut un jeton de support dans l'en-tête Authorization de chaque requête HTTPS adressée à votre point de terminaison. Exemple :

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

La chaîne AbCdEf123456 dans l'exemple précédent est le jeton d'autorisation du support. Il s'agit d'un jeton de chiffrement produit par Google. Le type de jeton de support et la valeur du champ audience dépendent du type d'audience d'authentification que vous avez sélectionné lors de la configuration de l'application Chat.

Si vous avez implémenté votre application de chat à l'aide de Cloud Functions ou de Cloud Run, Cloud IAM gère automatiquement la validation des jetons. Il vous suffit d'ajouter le compte de service Google Chat en tant que demandeur autorisé. Si votre application met en œuvre son propre serveur HTTP, vous pouvez vérifier votre jeton de support à l'aide d'une bibliothèque cliente des API Google Open Source:

Si le jeton n'est pas validé pour l'application Chat, votre service doit répondre à la requête avec un code de réponse HTTPS 401 (Unauthorized).

Authentifier les requêtes à l'aide de Cloud Functions ou de Cloud Run

Si votre logique de fonction est mise en œuvre à l'aide de Cloud Functions ou de Cloud Run, vous devez sélectionner App URL dans le champ Authentication Audience (Audience de l'authentification) du paramètre de connexion à l'application Chat et vous assurer que l'URL de l'application dans la configuration correspond à l'URL de la fonction Cloud ou du point de terminaison Cloud Run.

Vous devez ensuite autoriser le compte de service Google Chat chat@system.gserviceaccount.com en tant que demandeur.

Pour utiliser Cloud Functions (1re génération):

Console

Après avoir déployé votre fonction sur Google Cloud:

  1. Dans la console Google Cloud, accédez à la page Cloud Functions.

    Accéder à Cloud Functions

  2. Dans la liste Cloud Functions, cochez la case en regard de la fonction de réception. (Ne cliquez pas directement sur la fonction.)

  3. Cliquez sur Autorisations en haut de l'écran. Le panneau Autorisations s'affiche.

  4. Cliquez sur Ajouter un compte principal.

  5. Dans le champ Nouveaux comptes principaux, saisissez chat@system.gserviceaccount.com.

  6. Sélectionnez le rôle Cloud Functions > Demandeur Cloud Functions dans le menu déroulant Sélectionnez un rôle.

  7. Cliquez sur Enregistrer.

gcloud

Exécutez la commande gcloud functions add-iam-policy-binding :

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

Remplacez RECEIVING_FUNCTION par le nom de la fonction de votre application Chat.

Pour utiliser les services Cloud Functions (2e génération) ou Cloud Run, procédez comme suit:

Console

Après avoir déployé votre fonction ou votre service sur Google Cloud:

  1. Dans la console Google Cloud, accédez à la page Cloud Run:

    Accédez à Cloud Run

  2. Dans la liste des services Cloud Run, cochez la case en regard de la fonction de réception. (Ne cliquez pas directement sur la fonction.)

  3. Cliquez sur Autorisations en haut de l'écran. Le panneau Autorisations s'affiche.

  4. Cliquez sur Ajouter un compte principal.

  5. Dans le champ Nouveaux comptes principaux, saisissez chat@system.gserviceaccount.com.

  6. Sélectionnez le rôle Cloud Run > Demandeur Cloud Run dans le menu déroulant Sélectionnez un rôle.

  7. Cliquez sur Enregistrer.

gcloud

Exécutez la commande gcloud functions add-invoker-policy-binding :

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

Remplacez RECEIVING_FUNCTION par le nom de la fonction de votre application Chat.

Authentifier les requêtes avec un jeton d'ID d'URL d'application

Si le champ "Audience d'authentification" du paramètre de connexion de l'application Chat est défini sur App URL, le jeton d'autorisation du support dans la requête est un jeton d'ID OpenID Connect (OIDC) signé par Google. Le champ email est défini sur chat@system.gserviceaccount.com. Le champ audience est défini sur l'URL que vous avez configuré Google Chat pour envoyer des requêtes à votre application Chat. Par exemple, si le point de terminaison configuré de votre application Chat est https://example.com/app/, le champ audience du jeton d'ID est https://example.com/app/.

Les exemples suivants montrent comment vérifier que le jeton de support a été émis par Google Chat et ciblé votre application à l'aide de la bibliothèque cliente 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();

Authentifier les requêtes avec un JWT de numéro de projet

Si le champ "Audience d'authentification" du paramètre de connexion de l'application Chat est défini sur Project Number, le jeton d'autorisation du support dans la requête est un jeton Web JSON (JWT) autosigné, émis et signé par chat@system.gserviceaccount.com. Le champ audience est défini sur le numéro du projet Google Cloud que vous avez utilisé pour créer votre application Chat. Par exemple, si le numéro de projet Cloud de votre application Chat est 1234567890, le champ audience du jeton JWT est 1234567890.

Les exemples suivants montrent comment vérifier que le jeton de support a été émis par Google Chat et ciblé votre projet à l'aide de la bibliothèque cliente 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";

  // 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();