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]
- Extract the KID from the header:
424189bd9a18927f7ee924f1a100601f1524f441
- 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
- Verify JWT token using corresponded public key and passing audience and issuer options.
Complete solution
Dependencies
- jsonwebtoken@8.5.1
- jwks-rsa@2.0.4
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);
}
}
);
});
}
Top comments (1)
Thanks for that 🙏
I was struggling with this developers.google.com/chat/api/gui...
There is no Node example 😅
I have been able to reuse your example for my project!