notes.dt.in.th

How to make authenticated API calls to Google Cloud Function (2nd generation) or Google Cloud Run services from Cloudflare Workers (or other non-Node.js JavaScipt runtimes)

Google Cloud Function’s and Cloud Run’s documentation suggests using google-auth-library to make authenticated API calls. However, google-auth-library is made for Node.js. What if we want to make authenticated API calls from a non-Node.js runtime like Cloudflare Workers?

The basic steps are as follows:

  1. Extract relevant data from the JSON service account key file
  2. Generate a self-signed JWT
  3. Exchange that for a Google-signed JWT (the “ID token”)
  4. Use that JWT to invoke the service

JWT Flow

We will use the libraries jose to work with JWTs and redaxios to make HTTP requests.

import { importPKCS8, SignJWT } from 'jose'
import redaxios from 'redaxios'

Also assume that the endpoint we want to invoke is stored in the endpoint variable.

const endpoint = 'https://screenshotter-q3aroeegia-an.a.run.app/'

Extract relevant data from the JSON service account key file

Given the contents of a JSON service account key file in the serviceAccountFileContents variable, we extrac the following data:

const serviceAccount = JSON.parse(serviceAccountFileContents)
const privateKey = await importPKCS8(serviceAccount.private_key, 'RS256')
const email = serviceAccount.client_email

Generate a self-signed JWT

Generate a JWT according to the docs:

const payload = {
  iss: email,
  sub: email,
  aud: 'https://www.googleapis.com/oauth2/v4/token',
  target_audience: endpoint,
  iat: Math.floor(Date.now() / 1000),
  exp: Math.floor(Date.now() / 1000) + 3600,
}
const token = await new SignJWT(payload)
  .setProtectedHeader({ alg: 'RS256', typ: 'JWT' })
  .sign(privateKey)

Exchange the sign-signed JWT for a Google-signed JWT

Make a request to Google’s OAuth API according to the docs:

const idTokenResponse = await redaxios.post(
  'https://www.googleapis.com/oauth2/v4/token',
  {
    grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
    assertion: token,
  }
)
const idToken = idTokenResponse.data.id_token

Use the Google-signed JWT to invoke the service

const response = await redaxios.post(
  endpoint,
  {},
  {
    headers: {
      Authorization: `Bearer ${idToken}`,
    },
    responseType: 'stream',
  }
)

return new Response(response.data, {
  headers: response.headers,
  status: response.status,
  statusText: response.statusText,
})