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

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

インタラクション イベントを Chat 用アプリのエンドポイントにディスパッチするため、Google はサービスに HTTPS リクエストを送信します。リクエストが Google から送信されたことを確認するため、Chat は Google 署名付きの OpenID Connect(OIDC)ID トークンを、すべての HTTPS リクエストの Authorization ヘッダー(およびリクエスト本文の authorizationEventObject.systemIdToken フィールド)の署名なしトークンとして含めます。次に例を示します。

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

上記の例の文字列 AbCdEf123456 は、Bearer 認証トークンです。この暗号トークンは、Chat 用アプリの一意のプロジェクトごとのサービス アカウント(service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com)によって署名され、Chat 用アプリを構成するときに audience フィールドが Chat 用アプリ用に構成された HTTP エンドポイント URL に設定されます。

Chat 用アプリのサービス アカウントのメールアドレスは、Google Cloud コンソールの Chat API の [構成] タブの [接続設定] セクションからコピーできます。

  1. Google Cloud コンソールで、メニュー > [API とサービス] > [有効な API とサービス] > [Google Chat API] > [構成] に移動します。

    Google Chat API の構成に移動

  2. [インタラクティブ機能] > [接続設定] で、[サービス アカウントのメールアドレス](service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com)をコピーします。

Cloud Run 関数または Cloud Run を使用して Chat 用アプリを実装した場合、Chat 用アプリのサービス アカウントに Cloud Run Invoker(roles/run.invoker)ロールを付与すると、Cloud IAM がトークンの検証を自動的に処理します。アプリが独自の HTTP サーバーを実装している場合は、オープンソースの Google API クライアント ライブラリを使用して署名なしトークンを検証できます。

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

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

Cloud Run functions または Cloud Run を使用して関数ロジックが実装されている場合は、Chat 用アプリの接続設定の [トリガー] で構成されている HTTP エンドポイント URL が、Cloud Run functions のエンドポイントの URL に対応していることを確認してください。

次に、次の手順で、Chat 用アプリのサービス アカウント(Chat API の [構成] タブの [接続設定] セクションからコピーした service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com)を呼び出し元として承認します。

Console

関数またはサービスを Google Cloud にデプロイした後:

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

    Cloud Run に移動

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

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

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

  5. [新しいプリンシパル] フィールドに、Chat 用アプリのサービス アカウントのメールアドレス(service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.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:service-PROJECT_NUMBER@gcp-sa-gsuiteaddons.iam.gserviceaccount.com'

次のように置き換えます。

  • RECEIVING_FUNCTION: Chat 用アプリの関数の名前。
  • PROJECT_NUMBER: Chat 用アプリのサービス アカウントのメールアドレスから取得したプロジェクト番号。

ID トークンを使用して HTTP リクエストを認証する

HTTP エンドポイントの場合、リクエストのベアラー認証トークンは、Google 署名付きの OpenID Connect(OIDC)ID トークンです。email フィールドは Chat 用アプリのサービス アカウントのメールアドレス(service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com)に設定され、audience フィールドはリクエストを受信するように構成された HTTP エンドポイント URL に設定されます。たとえば、Chat 用アプリの構成済みエンドポイントが https://example.com/app/ の場合、ID トークンの audience フィールドは https://example.com/app/ になります。

HTTP エンドポイントが IAM ベースの認証をサポートするサービス(Cloud Run など)でホストされていない場合は、この認証方法をおすすめします。

次のサンプルは、Google OAuth クライアント ライブラリを使用して、署名なしトークンが Chat 用アプリに対して 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;
  }
}

アドオンではない Chat 用アプリ: Google Chat からのリクエストを検証する

次のドキュメントは、Google Workspace アドオンではない Chat 用アプリに適用されます。アドオンではない Chat 用アプリを移行するには、Google Chat 用アプリを Google Workspace アドオンに変換するをご覧ください。

[接続設定] で HTTP エンドポイント URL が構成されていないアドオン以外の Chat 用アプリの場合、署名なしトークンのタイプと audience フィールドの値は、Chat 用アプリの構成時に選択した認証オーディエンスのタイプによって異なります。リクエストは共有サービス アカウント chat@system.gserviceaccount.com によって署名されます。

Cloud Run functions を使用してリクエストを認証する(アドオンではない Chat 用アプリ)

関数ロジックが 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 用アプリ)

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

次のサンプルは、Google OAuth クライアント ライブラリを使用して、署名なしトークンが Google Chat によって発行され、アドオンではない 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 用アプリ)

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

次のサンプルは、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;
}