Verifying Google Chat request in NodeJS

Google Chat includes a bearer token in the Authorization header of every HTTPS Request to a bot. For example:

POST
Host: yourboturl.com
Authorization: Bearer %JWT%
Content-Type: application/json
User-Agent: Google-Dynamite

Decoded JWT token by…


This content originally appeared on DEV Community and was authored by Siarhei Hlasouski

Google Chat includes a bearer token in the Authorization header of every HTTPS Request to a bot. For example:

POST
Host: yourboturl.com
Authorization: Bearer %JWT%
Content-Type: application/json
User-Agent: Google-Dynamite

Decoded JWT token by jwt.io
Header:

{
  "alg": "RS256",
  "kid": "424189bd9a18927f7ee924f1a100601f1524f441",
  "typ": "JWT"
}

Payload:

{
  "aud": "1234567890",
  "exp": 1629047990,
  "iat": 1629044390,
  "iss": "chat@system.gserviceaccount.com"
}

All bearer tokens sent with requests from Google chat will have chat@system.gserviceaccount.com as the issuer, with the audience field specifying the target bot's project number from the Google API Console. For example, if the request is for a bot with the project number 1234567890, then the audience is 1234567890. [Verifying bot authenticity]

  1. Extract the KID from the header: 424189bd9a18927f7ee924f1a100601f1524f441
  2. Use the KID to find the matching public key in the JWKS (JSON Web Key Set) endpoint https://www.googleapis.com/service_accounts/v1/jwk/chat@system.gserviceaccount.com:

    {
      "keys": [
        {
          "use": "sig",
          "e": "AQAB",
          "kid": "7a06148a68d82d07a216be41934650062179cab2",
          "kty": "RSA",
          "n": "oIGHec8npfK8EG3kVj72jN8WKSGc-8QLMlT6Su_ie7D3AIHM6y-0Sj1OlEw6oIpWGE6oRCczm58uV2-AIOd-CXBWU-1fRMHCM36VpbicKgGKAH_HkRYDtpfCXXoZm5xtGPqV2EzE0OgOvY8_G829sCWM0HBuZdUnS7YDXq51N-Vj2NaFHSlvMZXfYKcZZ_c6oRuvTFWAGg-ce5AzFEusI8cJpjs3lTMQdmUqRC27Jr3wpvHsw51axcohwQ3WoMSnkWsptffnVaXhtHyRyXdUB4c70jb4Ytgr7ZRl33sYw9GmppXX49MJp_c5pxjr1t_tMRiOzmhfZcp-txOCqO653Q",
          "alg": "RS256"
        },
        {
          "n": "p1PCbDFnlDBaoDR7qlDxnZvSUHEoOlkrs1k5pTX3gKsgjdkj3CDkkJLKwziaNHhGE85pVwNZQb3DxtyjpOe9Bp9mKtTz1arDBYkaN6uNeCVVz41SO-6bXAKhdwzPQGobKe4N9g35nC85QhlmSbPuJuxr3Wmmz88w-keQAvaYOWm16P-zl8Dg5XQ2JXUVaUmEUv4dv2A3bE2BFBTYicJoLRQBX-oNk0K9wXokcmlGPS_NcjjCIcjWCYK8JWUMuWqnZCCBQVL3xfLt5ED4aC9SnpZ47bbEVrvUWe_7by4CqQ101DAbTg4VjLHSnIU8zmdCgfjA7dtfGg0NSulmNloCMw",
          "kty": "RSA",
          "alg": "RS256",
          "kid": "424189bd9a18927f7ee924f1a100601f1524f441",
          "e": "AQAB",
          "use": "sig"
        }
      ]
    }
    
  3. Verify JWT token using corresponded public key and passing audience and issuer options.

Complete solution

Dependencies

import { NextFunction, Request, Response, Router } from 'express';
import jwt from 'jsonwebtoken';
import { JwksClient } from 'jwks-rsa';

const GOOGLE_CHAT_PROJECT_NUMBER = '1234567890';

const jwksClient = new JwksClient({
  jwksUri:
    'https://www.googleapis.com/service_accounts/v1/jwk/chat@system.gserviceaccount.com',
  cache: true,
});

const router: Router = Router();

router.post('/google-chat/events', verificationRequestMiddleware(), async (req, res) => {
  // process google chat event
});

function verificationRequestMiddleware() {
  return async (request: Request, response: Response, next: NextFunction) => {
    const isVerified = await verifyRequest(request);

    if (!isVerified) {
      throw new UnauthorizedError('Authentication failed');
    }

    return next();
  };
}

async function verifyRequest(request: Request): Promise<boolean> {
  const prefix = 'Bearer ';
  const authHeader = request.header('Authorization') as string;
  const token = authHeader?.startsWith(prefix) ? authHeader.slice(prefix.length) : null;

  if (!token) {
    return false;
  }

  return new Promise<boolean>((resolve, reject) => {
    const getKey = (header, callback) => {
      jwksClient.getSigningKey(header.kid, (err, key) => {
        const signingKey = key.getPublicKey();
        callback(null, signingKey);
      });
    };

    jwt.verify(
      token,
      getKey,
      {
        audience: GOOGLE_CHAT_PROJECT_NUMBER,
        issuer: 'chat@system.gserviceaccount.com'
      },
      (err: any, decoded: any) => {
        if (err) {
          reject(false);
        } else {
          resolve(true);
        }
      }
    );
  });   
}


This content originally appeared on DEV Community and was authored by Siarhei Hlasouski


Print Share Comment Cite Upload Translate Updates
APA

Siarhei Hlasouski | Sciencx (2021-08-18T21:36:32+00:00) Verifying Google Chat request in NodeJS. Retrieved from https://www.scien.cx/2021/08/18/verifying-google-chat-request-in-nodejs/

MLA
" » Verifying Google Chat request in NodeJS." Siarhei Hlasouski | Sciencx - Wednesday August 18, 2021, https://www.scien.cx/2021/08/18/verifying-google-chat-request-in-nodejs/
HARVARD
Siarhei Hlasouski | Sciencx Wednesday August 18, 2021 » Verifying Google Chat request in NodeJS., viewed ,<https://www.scien.cx/2021/08/18/verifying-google-chat-request-in-nodejs/>
VANCOUVER
Siarhei Hlasouski | Sciencx - » Verifying Google Chat request in NodeJS. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/08/18/verifying-google-chat-request-in-nodejs/
CHICAGO
" » Verifying Google Chat request in NodeJS." Siarhei Hlasouski | Sciencx - Accessed . https://www.scien.cx/2021/08/18/verifying-google-chat-request-in-nodejs/
IEEE
" » Verifying Google Chat request in NodeJS." Siarhei Hlasouski | Sciencx [Online]. Available: https://www.scien.cx/2021/08/18/verifying-google-chat-request-in-nodejs/. [Accessed: ]
rf:citation
» Verifying Google Chat request in NodeJS | Siarhei Hlasouski | Sciencx | https://www.scien.cx/2021/08/18/verifying-google-chat-request-in-nodejs/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.