このセクションでは、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
フィールドの値は、Chat アプリの構成時に選択した認証オーディエンスのタイプによって異なります。
Cloud Functions または Cloud Run を使用して Chat 用アプリを実装している場合、Cloud IAM はトークンの検証を自動的に処理します。Google Chat サービス アカウントを承認済み起動元として追加するだけで済みます。アプリが独自の HTTP サーバーを実装している場合は、オープンソースの Google API クライアント ライブラリを使用して署名なしトークンを確認できます。
- Java: https://github.com/google/google-api-java-client
- Python: https://github.com/google/google-api-python-client
- Node.js: https://github.com/google/google-api-nodejs-client
- .NET: https://github.com/google/google-api-dotnet-client
Chat アプリのトークンが検証されない場合、サービスは HTTPS レスポンス コード 401 (Unauthorized)
でリクエストに応答する必要があります。
Cloud Functions または Cloud Run を使用してリクエストを認証する
関数のロジックが Cloud Functions または Cloud Run を使用して実装されている場合は、Chat アプリの接続設定の [Authentication Audience] フィールドで App URL
を選択し、構成のアプリ URL が Cloud Functions または Cloud Run エンドポイントの URL に対応していることを確認する必要があります。
次に、Google Chat サービス アカウント [email protected]
を起動元として承認する必要があります。
次の手順は、Cloud Functions(第 1 世代)を使用する方法を示しています。
コンソール
関数を Google Cloud にデプロイした後:
Google Cloud コンソールで、[Cloud Functions] ページに移動します。
[Cloud Functions] リストで、受信側関数の横にあるチェックボックスをオンにします。(関数自体はクリックしないでください)。
画面の上部の [権限] をクリックします。[権限] パネルが開きます。
[プリンシパルを追加] をクリックします。
[新しいプリンシパル] フィールドに「
[email protected]
」と入力します。[ロールを選択] プルダウン メニューから [Cloud Functions] > [Cloud Functions 起動元] ロールを選択します。
[保存] をクリックします。
gcloud
gcloud functions add-iam-policy-binding
コマンドを使用します。
gcloud functions add-iam-policy-binding RECEIVING_FUNCTION \
--member='serviceAccount:[email protected]' \
--role='roles/cloudfunctions.invoker'
RECEIVING_FUNCTION
は、Chat アプリの関数の名前に置き換えます。
次の手順は、Cloud Functions(第 2 世代)または Cloud Run サービスを使用する方法を示しています。
コンソール
関数またはサービスを Google Cloud にデプロイした後:
Google Cloud コンソールで、[Cloud Run] ページに移動します。
Cloud Run サービスリストで、受信側関数の横にあるチェックボックスをオンにします。(関数自体はクリックしないでください)。
画面の上部の [権限] をクリックします。[権限] パネルが開きます。
[プリンシパルを追加] をクリックします。
[新しいプリンシパル] フィールドに「
[email protected]
」と入力します。[ロールを選択] プルダウン メニューから [Cloud Run] > [Cloud Run 起動元] ロールを選択します。
[保存] をクリックします。
gcloud
gcloud functions add-invoker-policy-binding
コマンドを使用します。
gcloud functions add-invoker-policy-binding RECEIVING_FUNCTION \
--member='serviceAccount:[email protected]'
RECEIVING_FUNCTION
は、Chat アプリの関数の名前に置き換えます。
アプリ URL ID トークンを使用してリクエストを認証する
Chat アプリの接続設定の [Authentication Audience] フィールドが App URL
に設定されている場合、リクエストの署名なし認証トークンは Google によって署名された OpenID Connect(OIDC)ID トークンです。email
フィールドが [email protected]
に設定されています。audience
フィールドは、Chat アプリにリクエストを送信するように Google Chat を構成した URL に設定されます。たとえば、Chat アプリの構成済みエンドポイントが https://example.com/app/
の場合、ID トークンの audience
フィールドは https://example.com/app/
になります。
次のサンプルは、署名なしトークンが Google Chat によって発行され、Google OAuth クライアント ライブラリを使用してアプリをターゲットにしていることを確認する方法を示しています。
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 = "[email protected]";
// 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 = '[email protected]'
# 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 = '[email protected]';
// 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();
プロジェクト番号 JWT を使用してリクエストを認証する
Chat アプリの接続設定の Authentication Audience フィールドが Project
Number
に設定されている場合、リクエスト内の署名なし認証トークンは自己署名の JSON ウェブトークン(JWT)で、[email protected]
によって発行され、署名されます。audience
フィールドは、Chat アプリのビルドに使用した Google Cloud プロジェクト番号に設定されます。たとえば、Chat アプリの Cloud プロジェクト番号が 1234567890
の場合、JWT の audience
フィールドは 1234567890
になります。
次のサンプルは、署名なしトークンが Google Chat によって発行され、Google OAuth クライアント ライブラリを使用してプロジェクトをターゲットにしていることを確認する方法を示しています。
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 = "[email protected]";
// 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 = '[email protected]'
# 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 = '[email protected]';
// 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();
関連トピック
- Google Workspace での認証と認可の概要については、認証と認可の詳細をご覧ください。
- Chat での認証と認可の概要については、認証の概要をご覧ください。
- ユーザー認証情報またはサービス アカウントを使用して認証と認可を設定します。