Memverifikasi permintaan dari Google Chat

Untuk aplikasi Google Chat yang dibangun di endpoint HTTP, bagian ini menjelaskan cara memverifikasi bahwa permintaan ke endpoint Anda berasal dari Chat.

Untuk mengirim peristiwa interaksi ke endpoint aplikasi Chat Anda, Google membuat permintaan HTTPS ke layanan Anda. Untuk memverifikasi bahwa permintaan berasal dari Google, Chat menyertakan token ID OpenID Connect (OIDC) yang ditandatangani Google sebagai token pemilik di header Authorization setiap permintaan HTTPS (dan di kolom authorizationEventObject.systemIdToken isi permintaan). Contoh:

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

String AbCdEf123456 dalam contoh sebelumnya adalah token otorisasi pembawa. Token kriptografi ini ditandatangani oleh akun layanan unik per project aplikasi Chat Anda (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com), dan kolom audience disetel ke URL endpoint HTTP yang dikonfigurasi untuk aplikasi Chat Anda saat mengonfigurasi aplikasi Chat.

Anda dapat menyalin alamat email akun layanan aplikasi Chat dari bagian Setelan koneksi di tab Konfigurasi Chat API di Konsol Google Cloud:

  1. Di Konsol Google Cloud, buka Menu > APIs & Services > Enabled APIs & Services > Google Chat API > Configuration:

    Buka Konfigurasi Google Chat API

  2. Di bagian Fitur interaktif > Setelan koneksi, salin Email akun layanan (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com).

Jika Anda telah menerapkan aplikasi Chat menggunakan fungsi Cloud Run atau Cloud Run, Cloud IAM akan menangani verifikasi token secara otomatis saat Anda memberikan peran Cloud Run Invoker (roles/run.invoker) kepada akun layanan aplikasi Chat. Jika aplikasi Anda menerapkan server HTTP-nya sendiri, Anda dapat memverifikasi token pemilik menggunakan library klien Google API open source:

Jika token tidak diverifikasi untuk aplikasi Chat, layanan Anda harus merespons permintaan dengan kode respons HTTPS 401 (Unauthorized).

Mengautentikasi permintaan menggunakan Cloud Run Functions

Jika logika fungsi Anda diimplementasikan menggunakan fungsi Cloud Run atau Cloud Run, pastikan URL endpoint HTTP yang dikonfigurasi di bagian Pemicu dalam setelan koneksi aplikasi Chat sesuai dengan URL endpoint fungsi Cloud Run Anda.

Kemudian, beri otorisasi akun layanan aplikasi Chat Anda (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com, disalin dari bagian Setelan koneksi di tab Konfigurasi Chat API) sebagai pemanggil menggunakan langkah-langkah berikut:

Konsol

Setelah men-deploy fungsi atau layanan Anda ke Google Cloud:

  1. Di konsol Google Cloud, buka halaman Cloud Run:

    Buka Cloud Run

  2. Dalam daftar layanan Cloud Run, klik kotak centang di samping fungsi penerima. (Jangan mengklik fungsi.)

  3. Klik Permissions di bagian atas layar. Panel Permissions akan terbuka.

  4. Klik Tambahkan akun utama.

  5. Di kolom New principals, masukkan alamat email akun layanan aplikasi Chat Anda (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com).

  6. Dari menu Select a role, pilih peran Cloud Run

    Cloud Run Invoker.

  7. Klik Simpan.

gcloud

Gunakan perintah 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'

Ganti kode berikut:

  • RECEIVING_FUNCTION: nama fungsi aplikasi Chat Anda.
  • PROJECT_NUMBER: nomor project dari alamat email akun layanan aplikasi Chat Anda.

Mengautentikasi permintaan HTTP dengan Token ID

Untuk endpoint HTTP, token otorisasi pembawa dalam permintaan adalah token ID OpenID Connect (OIDC) yang ditandatangani Google. Kolom email ditetapkan ke alamat email akun layanan aplikasi Chat Anda (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com), dan kolom audience ditetapkan ke URL endpoint HTTP yang dikonfigurasi untuk menerima permintaan. Misalnya, jika endpoint yang dikonfigurasi untuk aplikasi Chat Anda adalah https://example.com/app/, maka kolom audience dalam token ID adalah https://example.com/app/.

Ini adalah metode autentikasi yang direkomendasikan jika endpoint HTTP Anda tidak dihosting di layanan yang mendukung autentikasi berbasis IAM (seperti Cloud Run).

Contoh berikut menunjukkan cara memverifikasi bahwa token pembawa dikeluarkan oleh Google untuk aplikasi Chat Anda dan ditujukan ke endpoint aplikasi Anda menggunakan library klien OAuth 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;
  }
}

Aplikasi Chat yang bukan add-on: Memverifikasi permintaan dari Google Chat

Dokumentasi berikut berlaku untuk aplikasi Chat yang bukan add-on Google Workspace. Untuk memigrasikan aplikasi Chat yang bukan add-on, lihat Mengonversi aplikasi Google Chat menjadi add-on Google Workspace.

Untuk aplikasi Chat yang bukan add-on yang dikonfigurasi dengan URL endpoint HTTP di bagian Setelan koneksi, jenis token pembawa dan nilai kolom audience bergantung pada jenis Audiens Autentikasi yang Anda pilih saat mengonfigurasi aplikasi Chat, dan permintaan ditandatangani oleh akun layanan bersama chat@system.gserviceaccount.com.

Mengautentikasi permintaan menggunakan fungsi Cloud Run (Aplikasi Chat yang bukan add-on)

Jika logika fungsi Anda diimplementasikan menggunakan fungsi Cloud Run, Anda harus memilih URL endpoint HTTP di kolom Audiens Autentikasi pada setelan koneksi aplikasi Chat dan pastikan bahwa URL endpoint HTTP dalam konfigurasi sesuai dengan URL endpoint fungsi Cloud Run.

Kemudian, Anda perlu memberi otorisasi pada akun layanan Google Chat chat@system.gserviceaccount.com sebagai pemanggil menggunakan langkah-langkah berikut:

Konsol

Setelah men-deploy fungsi atau layanan Anda ke Google Cloud:

  1. Di konsol Google Cloud, buka halaman Cloud Run:

    Buka Cloud Run

  2. Dalam daftar layanan Cloud Run, klik kotak centang di samping fungsi penerima. (Jangan mengklik fungsi.)

  3. Klik Permissions di bagian atas layar. Panel Permissions akan terbuka.

  4. Klik Tambahkan akun utama.

  5. Di kolom New principals, masukkan chat@system.gserviceaccount.com.

  6. Dari menu Select a role, pilih peran Cloud Run

    Cloud Run Invoker.

  7. Klik Simpan.

gcloud

Gunakan perintah gcloud functions add-invoker-policy-binding:

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

Ganti RECEIVING_FUNCTION dengan nama fungsi aplikasi Chat Anda.

Mengautentikasi permintaan HTTP dengan Token ID (Aplikasi Chat yang bukan add-on)

Jika kolom Audiens Autentikasi dari aplikasi Chat yang bukan add-on setelan koneksi disetel ke URL endpoint HTTP, token otorisasi pembawa dalam permintaan adalah token ID OpenID Connect (OIDC) yang ditandatangani Google. Kolom email ditetapkan ke chat@system.gserviceaccount.com. Kolom Audiens Autentikasi disetel ke URL yang Anda konfigurasi agar Google Chat dapat mengirim permintaan ke aplikasi Chat Anda yang bukan add-on. Misalnya, jika endpoint aplikasi Chat yang dikonfigurasi adalah https://example.com/app/, maka kolom Authentication Audience di token ID adalah https://example.com/app/.

Contoh berikut menunjukkan cara memverifikasi bahwa token pembawa dikeluarkan oleh Google Chat dan ditujukan untuk aplikasi Chat Anda yang bukan add-on menggunakan library klien 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;
}

Mengautentikasi permintaan dengan JWT Nomor Project (Aplikasi chat yang bukan add-on)

Jika kolom Audiens Autentikasi dari aplikasi Chat yang bukan add-on setelan koneksi disetel ke Project Number, token otorisasi pembawa dalam permintaan adalah Token Web JSON (JWT) yang ditandatangani sendiri, dikeluarkan dan ditandatangani oleh chat@system.gserviceaccount.com. Kolom audience disetel ke nomor project Google Cloud yang Anda gunakan untuk membangun aplikasi Chat yang bukan add-on. Misalnya, jika nomor project Cloud aplikasi Chat Anda adalah 1234567890, maka kolom audience di JWT adalah 1234567890.

Contoh berikut menunjukkan cara memverifikasi bahwa token pemilik dikeluarkan oleh Google Chat dan ditujukan untuk project Anda menggunakan library klien 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;
}