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 envoyées à votre point de terminaison proviennent de Chat.

Pour distribuer les événements d'interaction au point de terminaison de votre application Chat, Google envoie des requêtes HTTPS à votre service. Pour vérifier que la requête provient de Google, Chat inclut un jeton d'identification OpenID Connect (OIDC) signé par Google en tant que jeton du porteur dans l'en-tête Authorization de chaque requête HTTPS (et dans le champ authorizationEventObject.systemIdToken du corps de la requête). 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 porteur. Ce jeton cryptographique est signé par le compte de service unique de votre application Chat par projet (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com). Le champ audience est défini sur l'URL du point de terminaison HTTP configurée pour votre application Chat lors de la configuration de l'application Chat.

Vous pouvez copier l'adresse e-mail du compte de service de votre application Chat depuis la section Paramètres de connexion de l'onglet Configuration de l'API Chat dans la console Google Cloud :

  1. Dans la console Google Cloud, accédez à Menu > API et services > API et services activés > API Google Chat > Configuration :

    Accéder à la configuration de l'API Google Chat

  2. Sous Fonctionnalités interactives > Paramètres de connexion, copiez l'adresse e-mail du compte de service (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com).

Si vous avez implémenté votre application Chat à l'aide de fonctions Cloud Run ou de Cloud Run, Cloud IAM gère automatiquement la validation des jetons lorsque vous accordez le rôle Cloud Run Invoker (roles/run.invoker) au compte de service de l'application Chat. Si votre application implémente son propre serveur HTTP, vous pouvez valider le jeton du porteur à l'aide d'une bibliothèque cliente de l'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 Run Functions

Si la logique de votre fonction est implémentée à l'aide de Cloud Run Functions ou Cloud Run, assurez-vous que les URL des points de terminaison HTTP configurées sous Déclencheurs dans les paramètres de connexion de l'application Chat correspondent à l'URL du point de terminaison de votre fonction Cloud Run.

Ensuite, autorisez le compte de service de votre application Chat (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com, copié depuis la section Paramètres de connexion de l'onglet Configuration de l'API Chat) en tant qu'appelant en suivant les étapes suivantes :

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 l'adresse e-mail du compte de service de votre application Chat (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com).

  6. Dans le menu Sélectionner un rôle, sélectionnez le rôle Cloud Run.

    Demandeur Cloud Run

  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:service-PROJECT_NUMBER@gcp-sa-gsuiteaddons.iam.gserviceaccount.com'

Remplacez les éléments suivants :

  • RECEIVING_FUNCTION : nom de la fonction de votre application Chat.
  • PROJECT_NUMBER : numéro de projet figurant dans l'adresse e-mail du compte de service de votre application Chat.

Authentifier les requêtes HTTP avec un jeton d'identité

Pour les points de terminaison HTTP, le jeton d'autorisation du porteur dans la requête est un jeton d'identification OpenID Connect (OIDC) signé par Google. Le champ email est défini sur l'adresse e-mail du compte de service de votre application Chat (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com), et le champ audience est défini sur l'URL du point de terminaison HTTP configurée pour recevoir la requête. Par exemple, si le point de terminaison configuré de votre application Chat est https://example.com/app/, le champ audience du jeton d'identité est https://example.com/app/.

Il s'agit de la méthode d'authentification recommandée si votre point de terminaison HTTP n'est pas hébergé sur un service compatible avec l'authentification basée sur IAM (comme Cloud Run).

Les exemples suivants montrent comment vérifier que le jeton de support a été émis par Google pour votre application Chat et qu'il est destiné au point de terminaison de votre application à l'aide de la bibliothèque cliente Google OAuth :

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;
  }
}

Applications Chat qui ne sont pas des modules complémentaires : valider les requêtes provenant de Google Chat

La documentation suivante s'applique aux applications Chat qui ne sont pas des modules complémentaires Google Workspace. Pour migrer une application Chat qui n'est pas un module complémentaire, consultez Convertir une application Google Chat en module complémentaire Google Workspace.

Pour les applications Chat qui ne sont pas des modules complémentaires configurés avec une URL de point de terminaison HTTP sous Paramètres de connexion, le type du 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. Les requêtes sont signées par le compte de service partagé chat@system.gserviceaccount.com.

Authentifier les requêtes à l'aide de Cloud Run Functions (applications Chat qui ne sont pas des modules complémentaires)

Si la logique de votre fonction est implémentée à l'aide de fonctions Cloud Run, vous devez sélectionner URL du point de terminaison HTTP dans le champ Audience d'authentification du paramètre de connexion de l'application Chat et vous assurer que l'URL du point de terminaison HTTP dans la configuration correspond à l'URL du point de terminaison de la fonction Cloud Run.

Vous devez ensuite autoriser le compte de service Google Chat chat@system.gserviceaccount.com en tant qu'appelant en suivant les étapes ci-dessous :

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. Dans le menu Sélectionner un rôle, sélectionnez le rôle Cloud Run.

    Demandeur Cloud Run

  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 HTTP avec un jeton d'identité (applications Chat qui ne sont pas des modules complémentaires)

Si le champ Audience d'authentification du paramètre de connexion d'une application Chat qui n'est pas un module complémentaire est défini sur URL du point de terminaison HTTP, le jeton d'autorisation du porteur dans la requête est un jeton d'identification OpenID Connect (OIDC) signé par Google. Le champ email est défini sur chat@system.gserviceaccount.com. Le champ Audience d'authentification est défini sur l'URL que vous avez configurée pour que Google Chat envoie des requêtes à votre application Chat qui n'est pas un module complémentaire. Par exemple, si le point de terminaison configuré de votre application Chat est https://example.com/app/, le champ Audience d'authentification 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é sur votre application Chat qui n'est pas un module complémentaire à l'aide de la bibliothèque cliente 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;
}

Authentifier les requêtes avec un JWT de numéro de projet (applications de chat qui ne sont pas des modules complémentaires)

Si le champ Audience d'authentification du paramètre de connexion de l'application Chat qui n'est pas un module complémentaire est défini sur Project Number, le jeton d'autorisation du porteur 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 qui n'est pas un module complémentaire. Par exemple, si le numéro de projet Cloud de votre application Chat est 1234567890, le champ audience du JWT est 1234567890.

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