Verify requests from Google Chat

  • Google Chat sends a bearer token in the Authorization header of HTTPS requests to verify that the request originates from Google.

  • Cloud Functions and Cloud Run automatically handle token verification when you add the Google Chat service account as an authorized invoker.

  • For apps with their own HTTP server, you can verify the bearer token using a Google API client library or by validating the ID token or JWT based on the authentication audience configuration.

  • If token verification fails, your service should respond with an HTTPS 401 (Unauthorized) response code.

  • The authentication audience determines whether the bearer token is an ID token (for HTTP endpoint URLs) or a JWT (for Project Numbers), impacting the verification process.

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 HTTPS requests to your service. To verify that the request is coming from Google, Chat includes a Google-signed OpenID Connect (OIDC) ID token as a bearer token in the Authorization header of every HTTPS request (and in the authorizationEventObject.systemIdToken field of the request body). 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 cryptographic token is signed by your Chat app's unique, per-project service account (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com), and the audience field is set to the HTTP endpoint URL configured for your Chat app when configuring the Chat app.

You can copy your Chat app's service account email address from the Connection settings section of the Chat API Configuration tab in the Google Cloud console:

  1. In the Google Cloud console, go to Menu > APIs & Services > Enabled APIs & Services > Google Chat API > Configuration:

    Go to Google Chat API Configuration

  2. Under Interactive features > Connection settings, copy the Service account email (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com).

If you've implemented your Chat app using Cloud Run functions or Cloud Run, Cloud IAM handles token verification automatically when you grant the Chat app's service account the Cloud Run Invoker (roles/run.invoker) role. If your app implements its own HTTP server, you can verify the 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 Run functions

If your function logic is implemented using Cloud Run functions or Cloud Run, make sure that the HTTP endpoint URLs configured under Triggers in the Chat app connection settings correspond to the URL of your Cloud Run function endpoint.

Then, authorize your Chat app's service account (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com, copied from the Connection settings section of the Chat API Configuration tab) as an invoker using the following steps:

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 your Chat app's service account email address (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com).

  6. From the Select a role menu, select the role Cloud Run

    Cloud Run Invoker.

  7. Click Save.

gcloud

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

gcloud functions add-invoker-policy-binding RECEIVING_FUNCTION \
  --member='serviceAccount:service-PROJECT_NUMBER@gcp-sa-gsuiteaddons.iam.gserviceaccount.com'

Replace the following:

  • RECEIVING_FUNCTION: the name of your Chat app's function.
  • PROJECT_NUMBER: the project number from your Chat app's service account email address.

Authenticate HTTP requests with an ID Token

For HTTP endpoints, the bearer authorization token in the request is a Google-signed OpenID Connect (OIDC) ID token. The email field is set to your Chat app's service account email address (service-<var>PROJECT_NUMBER</var>@gcp-sa-gsuiteaddons.iam.gserviceaccount.com), and the audience field is set to the HTTP endpoint URL configured to receive the request. 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/.

This is the recommended authentication method if your HTTP endpoint isn't hosted on a service that supports IAM-based authentication (such as Cloud Run).

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

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 apps that aren't add-ons: Verify requests from Google Chat

The following documentation applies to Chat apps that aren't Google Workspace add-ons. To migrate a Chat app that isn't an add-on, see Convert a Google Chat app to a Google Workspace add-on.

For Chat apps that aren't add-ons configured with HTTP endpoint URL under Connection settings, 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, and requests are signed by the shared service account chat@system.gserviceaccount.com.

Authenticate requests using Cloud Run functions (Chat apps that aren't add-ons)

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

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

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. From the Select a role menu, select the role Cloud Run

    Cloud Run Invoker.

  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 HTTP requests with an ID Token (Chat apps that aren't add-ons)

If the Authentication Audience field of the Chat app that isn't an add-on connection setting is set to HTTP endpoint 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 Authentication Audience field is set to the URL you configured Google Chat to send requests to your Chat app that isn't an add-on. For example, if the configured endpoint of your Chat app is https://example.com/app/, then the Authentication 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 Chat app that isn't an add-on 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 (Chat apps that aren't add-ons)

If the Authentication Audience field of the Chat app that isn't an add-on 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 that isn't an add-on. 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;
}