Google Chat からのリクエストを確認する

このセクションでは、HTTP エンドポイント上に構築された Google Chat アプリの場合に、エンドポイントへのリクエストが Chat から送信されたことを確認する方法について説明します。

インタラクション イベントを Chat アプリのエンドポイントにディスパッチするために、Google はサービスにリクエストを送信します。リクエストが Google から送信されたことを確認するために、Chat はエンドポイントへのすべての HTTPS リクエストの Authorization ヘッダーに ベアラートークン を含めます。次に例を示します。

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

上記の例の文字列 AbCdEf123456 は、ベアラ認証トークンです。これは、Google によって生成された暗号トークンです。ベアラートークンのタイプと audience フィールドの値は、認証オーディエンスのタイプによって異なります。

Cloud Run functions を使用して Chat アプリを実装している場合、Cloud IAM はトークンの検証を自動的に処理します。Google Chat サービス アカウントを認可された呼び出し元として追加する必要があります。 アプリが独自の HTTP サーバーを実装している場合は、オープンソースの Google API クライアント ライブラリを使用してベアラートークン を検証できます。

トークンが Chat アプリで検証されない場合、サービスは HTTPS レスポンス コード 401 (Unauthorized) でリクエストに応答する必要があります。

Cloud Run functions を使用してリクエストを認証する

関数のロジックが Cloud Run functions を使用して実装されている場合は、Chat アプリの接続設定の [認証オーディエンス] フィールドで [HTTP エンドポイント URL] を選択し、構成内の HTTP エンドポイント URL が Cloud Run functions のエンドポイントの URL に対応していることを確認する必要があります。

次に、次の手順で Google Chat サービス アカウント chat@system.gserviceaccount.com を呼び出し元として認可する必要があります。

Console

関数またはサービスを Google Cloud にデプロイしたら、次の操作を行います。

  1. Google Cloud コンソールで [Cloud Run] ページに移動します。

    Cloud Run に移動

  2. Cloud Run サービスリストで、受信側関数の横にあるチェックボックスをオンにします (関数自体はクリックしないでください)。

  3. 画面の上部の [権限] をクリックします。[権限] パネルが開きます。

  4. [プリンシパルを追加] をクリックします。

  5. [新しいプリンシパル] フィールドに「chat@system.gserviceaccount.com」と入力します。

  6. [ロールを選択] メニューから、ロール [Cloud Run] を選択します

    [Cloud Run 起動元]。

  7. [保存] をクリックします。

gcloud

gcloud functions add-invoker-policy-binding コマンドを使用します。

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

RECEIVING_FUNCTION は、Chat アプリの関数の名前に置き換えます。

ID トークンで HTTP リクエストを認証する

Chat アプリの接続設定の [認証オーディエンス] フィールドが [HTTP エンドポイント URL] に設定されている場合、リクエストのベアラ認証トークンは Google 署名付き OpenID Connect(OIDC)[ID トークン] です。email フィールドは chat@system.gserviceaccount.com に設定されます。[認証オーディエンス] フィールドは、Google Chat がリクエストを Chat アプリに送信するように構成した URL に設定されます。たとえば、Chat アプリの構成済みエンドポイントが https://example.com/app/ の場合、ID トークンの [認証オーディエンス] フィールドは https://example.com/app/ になります。

HTTP エンドポイントが IAM ベースの認証(Cloud Run など)をサポートするサービスでホストされていない場合は、この認証方法をおすすめします。この方法を使用する場合、HTTP サービスは実行中のエンドポイントの URL に関する情報を必要としますが、Cloud プロジェクト番号に関する情報は必要ありません。

次のサンプルは、Google OAuth クライアント ライブラリを使用して、ベアラートークンが Google Chat によって発行され、アプリをターゲットにしていることを確認する方法を示しています。

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

プロジェクト番号 JWT でリクエストを認証する

Chat アプリの 接続設定の [認証オーディエンス] フィールドが Project Number に設定されている場合、リクエストのベアラ認証トークンは、chat@system.gserviceaccount.com によって発行および署名された自己署名 JSON ウェブトークン(JWT)です。 audience フィールドは、Chat アプリのビルドに使用した Google Cloud プロジェクト番号に設定されます。たとえば、Chat アプリの Cloud プロジェクト番号が 1234567890 の場合、JWT の audience フィールドは 1234567890 になります。

この認証方法は、HTTP エンドポイント URL の代わりに Cloud プロジェクト番号を使用してリクエストを検証する場合にのみおすすめします。たとえば、同じ Cloud プロジェクト番号を維持しながらエンドポイント URL を変更する場合や、複数の Cloud プロジェクト番号に同じエンドポイントを使用し、audience フィールドを Cloud プロジェクト番号のリストと比較する場合などです。

次のサンプルは、Google OAuth クライアント ライブラリを使用して、ベアラートークンが Google Chat によって発行され、プロジェクトをターゲットにしていることを確認する方法を示しています。

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