Verify requests from Google Chat

For Google Chat apps built on HTTP endpoints, this section explains how to verify that the requests to your endpoint come from Chat.

To dispatch interaction events to your Chat app's endpoint, Google makes requests to your service. To verify that the request is coming from Google, Chat includes a bearer token in the Authorization header of every HTTPS request to your endpoint. For example:

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

The string AbCdEf123456 in the preceding example is the bearer authorization token. This is a cryptographic token produced by Google. The type of the bearer token and the value of the audience field depend on the type of authentication audience you selected when configuring the Chat app.

If you've implemented your Chat app using Cloud Functions or Cloud Run, Cloud IAM handles token verification automatically. You just need to add the Google Chat service account as an authorized invoker. If your app implements its own HTTP server, you can verify your bearer token using an open source Google API client library:

If the token doesn't verify for the Chat app, your service should respond to the request with an HTTPS response code 401 (Unauthorized).

Authenticate requests using Cloud Functions or Cloud Run

If your function logic is implemented using Cloud Functions or Cloud Run, you must select App URL in the Authentication Audience field of the Chat app connection setting and make sure that the app URL in the configuration corresponds to the URL of the Cloud Function or Cloud Run endpoint.

Then, you need to authorize the Google Chat service account chat@system.gserviceaccount.com as an invoker.

The following steps shows how to use Cloud Functions (1st gen):

Console

After deploying your function to Google Cloud:

  1. In the Google Cloud console, go to the Cloud Functions page:

    Go to Cloud Functions

  2. In the Cloud Functions list, click the checkbox next to the receiving function. (Don't click the function itself.)

  3. Click Permissions at the top of the screen. The Permissions panel opens.

  4. Click Add principal.

  5. In the New principals field, enter chat@system.gserviceaccount.com.

  6. Select the role Cloud Functions > Cloud Functions Invoker from the Select a role drop-down menu.

  7. Click Save.

gcloud

Use the gcloud functions add-iam-policy-binding command:

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

Replace RECEIVING_FUNCTION with the name of your Chat app's function.

The following steps shows how to use Cloud Functions (2nd gen) or Cloud Run services:

Console

After deploying your function or service to Google Cloud:

  1. In the Google Cloud console, go to the Cloud Run page:

    Go to Cloud Run

  2. In the Cloud Run services list, click the checkbox next to the receiving function. (Don't click the function itself.)

  3. Click Permissions at the top of the screen. The Permissions panel opens.

  4. Click Add principal.

  5. In the New principals field, enter chat@system.gserviceaccount.com.

  6. Select the role Cloud Run > Cloud Run Invoker from the Select a role drop-down menu.

  7. Click Save.

gcloud

Use the gcloud functions add-invoker-policy-binding command:

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

Replace RECEIVING_FUNCTION with the name of your Chat app's function.

Authenticate requests with an App URL ID Token

If the Authentication Audience field of the Chat app connection setting is set to App URL, the bearer authorization token in the request is a Google-signed OpenID Connect (OIDC) ID token. The email field is set to chat@system.gserviceaccount.com. The audience field is set to the URL you configured Google Chat to send requests to your Chat app. For example, if the configured endpoint of your Chat app is https://example.com/app/, then the audience field in the ID token is https://example.com/app/.

The following samples show how to verify that the bearer token was issued by Google Chat and targeted at your app using the Google OAuth client library.

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

Authenticate requests with a Project Number JWT

If the Authentication Audience field of the Chat app connection setting is set to Project Number, the bearer authorization token in the request is a self-signed JSON Web Token (JWT), issued and signed by chat@system.gserviceaccount.com. The audience field is set to the Google Cloud project number that you used to build your Chat app. For example, if your Chat app's Cloud project number is 1234567890, then the audience field in the JWT is 1234567890.

The following samples show how to verify that the bearer token was issued by Google Chat and targeted at your project using the Google OAuth client library.

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