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 akan membuat permintaan ke layanan Anda. Untuk memverifikasi bahwa permintaan berasal dari Google, Chat menyertakan token pemilik di header Authorization setiap permintaan HTTPS ke endpoint Anda. 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. Ini adalah token kriptografis yang dibuat oleh Google. Jenis token pemilik dan nilai kolom audience bergantung pada jenis audiens autentikasi yang Anda pilih saat mengonfigurasi aplikasi Chat.

Jika Anda telah menerapkan aplikasi Chat menggunakan Cloud Functions atau Cloud Run, Cloud IAM menangani verifikasi token secara otomatis. Anda hanya perlu menambahkan akun layanan Google Chat sebagai invoker resmi. Jika aplikasi Anda mengimplementasikan 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 Functions atau Cloud Run

Jika logika fungsi diimplementasikan menggunakan Cloud Functions atau Cloud Run, Anda harus memilih App URL di kolom Authentication Audience di setelan koneksi aplikasi Chat dan memastikan URL aplikasi dalam konfigurasi sesuai dengan URL endpoint Cloud Function atau Cloud Run.

Kemudian, Anda perlu memberi otorisasi ke akun layanan Google Chat chat@system.gserviceaccount.com sebagai invoker.

Langkah-langkah berikut menunjukkan cara menggunakan Cloud Functions (generasi ke-1):

Konsol

Setelah men-deploy fungsi Anda ke Google Cloud:

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

    Buka Cloud Functions

  2. Dalam daftar Cloud Functions, klik kotak centang di samping fungsi penerima. (Jangan klik fungsi itu sendiri.)

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

  4. Klik Tambahkan akun utama.

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

  6. Pilih peran Cloud Functions > Cloud Functions Invoker dari menu drop-down Select a role.

  7. Klik Save.

gcloud

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

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

Ganti RECEIVING_FUNCTION dengan nama fungsi aplikasi Chat Anda.

Langkah-langkah berikut menunjukkan cara menggunakan layanan Cloud Functions (generasi ke-2) atau Cloud Run:

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 klik fungsi itu sendiri.)

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

  4. Klik Tambahkan akun utama.

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

  6. Pilih peran Cloud Run > Cloud Run Invoker dari menu drop-down Pilih peran.

  7. Klik Save.

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 dengan Token ID URL Aplikasi

Jika kolom Authentication Audience di setelan koneksi aplikasi Chat ditetapkan ke App URL, token otorisasi pemilik dalam permintaan adalah token ID OpenID Connect (OIDC) yang ditandatangani oleh Google. Kolom email disetel ke chat@system.gserviceaccount.com. Kolom audience disetel ke URL yang Anda konfigurasikan Google Chat untuk mengirim permintaan ke aplikasi Chat Anda. Misalnya, jika endpoint yang dikonfigurasi untuk aplikasi Chat Anda adalah https://example.com/app/, kolom audience dalam token ID adalah https://example.com/app/.

Contoh berikut menunjukkan cara memverifikasi bahwa token pemilik dikeluarkan oleh Google Chat dan ditargetkan di aplikasi Anda menggunakan library klien OAuth 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";

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

Mengautentikasi permintaan dengan JWT Nomor Project

Jika kolom Authentication Audience di setelan koneksi aplikasi Chat ditetapkan ke Project Number (atau tidak ditetapkan), token otorisasi pemilik dalam permintaan adalah JSON Web Token (JWT) yang ditandatangani sendiri, yang dikeluarkan dan ditandatangani oleh chat@system.gserviceaccount.com. Kolom audience ditetapkan ke nomor project Google Cloud yang Anda gunakan untuk membangun aplikasi Chat. 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 ditargetkan di project Anda menggunakan library klien OAuth 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();