Skip to content

Instantly share code, notes, and snippets.

@KeKs0r
Created January 24, 2023 14:11
Show Gist options
  • Save KeKs0r/92be7af08d1d10eae8d1328c78de5f07 to your computer and use it in GitHub Desktop.
Save KeKs0r/92be7af08d1d10eae8d1328c78de5f07 to your computer and use it in GitHub Desktop.
Get JWT Token from service account for GCP (to use Google Api in cloudflare workers)
import { subtle } from 'crypto'
import { Base64 } from 'js-base64'
type ServiceAccount = {
private_key_id: string
private_key: string
client_email: string
}
type Scope = 'https://documentai.googleapis.com/'
type TokenOptions = {
aud: Scope
}
export async function getJWTFromServiceAccount(sa: ServiceAccount, { aud }: TokenOptions) {
const privateKey = await importPrivateKey(sa.private_key)
const header = Base64.encodeURI(
JSON.stringify({
alg: 'RS256',
typ: 'JWT',
kid: sa.private_key_id,
})
)
const iat = Math.floor(Date.now() / 1000)
const exp = iat + 3600
const payload = Base64.encodeURI(
JSON.stringify({
iss: sa.client_email,
sub: sa.client_email,
aud,
exp,
iat,
})
)
const textEncoder = new TextEncoder()
const inputArrayBuffer = textEncoder.encode(`${header}.${payload}`)
const outputArrayBuffer = await subtle.sign(
{ name: 'RSASSA-PKCS1-v1_5' },
privateKey,
inputArrayBuffer
)
const signature = Base64.fromUint8Array(new Uint8Array(outputArrayBuffer), true)
const token = `${header}.${payload}.${signature}`
return token
}
function importPrivateKey(pem: string) {
const pemContents = getPemContent(pem)
const buffer = Base64.toUint8Array(pemContents)
const algorithm = {
name: 'RSASSA-PKCS1-v1_5',
hash: {
name: 'SHA-256',
},
}
return subtle.importKey('pkcs8', buffer, algorithm, false, ['sign'])
}
function getPemContent(rawPem: string) {
const pemHeader = '-----BEGIN PRIVATE KEY-----'
const pemFooter = '-----END PRIVATE KEY-----'
const pem = rawPem.replace(/\n/g, '')
if (!pem.startsWith(pemHeader) || !pem.endsWith(pemFooter)) {
throw new Error('Invalid service account private key')
}
const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length)
return pemContents
}
@heshamMassoud
Copy link

You sure this works on a cloudflare worker? I am getting the following error:

  The package "crypto" wasn't found on the file system but is built into node. Are you trying to
  bundle for node? You can use "platform: 'node'" to do that, which will remove this error.

@KeKs0r
Copy link
Author

KeKs0r commented Jun 16, 2023

@heshamMassoud I think you need nodejs compatibility in your wrangler.toml:

compatibility_flags = [ "streams_enable_constructors", "nodejs_compat" ]

@arjunyel
Copy link

I haven't tried it yet but instead of

import { subtle } from 'crypto'

You should just be able to use it from globalThis

return globalThis.crypto.subtle.importKey('pkcs8', buffer, algorithm, false, ['sign'])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment