@sagi.io/workers-jwt
helps you
generate a JWT
on Cloudflare Workers with the WebCrypto API. Helper function for GCP Service Accounts included.
โญ We use it at OpenSay to efficiently access Google's REST APIs with 1 round trip.
$ npm i @sagi.io/workers-jwt
We currently expose two methods: getToken
for general purpose JWT
generation
and getTokenFromGCPServiceAccount
for JWT
generation using a GCP
service account.
Function definition:
const getToken = async ({
privateKeyPEM,
payload,
alg = 'RS256',
cryptoImpl = null,
headerAdditions = {},
}) => { ... }
Where:
privateKeyPEM
is the private keystring
inPEM
format.payload
is theJSON
payload to be signed, i.e. the{ aud, iat, exp, iss, sub, scope, ... }
.alg
is the signing algorithm as defined inRFC7518
, currently onlyRS256
andES256
are supported.cryptoImpl
is aWebCrypto
API
implementation. Cloudflare Workers supportWebCrypto
out of the box. ForNode.js
you can use [require('crypto').webcrypto
- see examples below and in the tests.headerAdditions
is an object with keys and string values to be added to the header of theJWT
.
Function definition:
const getTokenFromGCPServiceAccount = async ({
serviceAccountJSON,
aud,
alg = 'RS256',
cryptoImpl = null,
expiredAfter = 3600,
headerAdditions = {},
payloadAdditions = {}
}) => { ... }
Where:
serviceAccountJSON
is the service accountJSON
object .aud
is the audience field in theJWT
's payload. e.g.https://www.googleapis.com/oauth2/v4/token
'.expiredAfter
- the duration of the token's validity. Defaults to 1 hour - 3600 seconds.payloadAdditions
is an object with keys and string values to be added to the payload of theJWT
. Example -{ scope: 'https://www.googleapis.com/auth/chat.bot' }
.alg
,cryptoImpl
,headerAdditions
are defined as above.
Suppose you'd like to use Firestore
's REST API. The first step is to generate
a service account with the "Cloud Datastore User" role. Please download the
service account and store its contents in the SERVICE_ACCOUNT_JSON_STR
environment
variable.
The aud
is defined by GCP's service definitions
and is simply the following concatenated string: 'https://' + SERVICE_NAME + '/' + API__NAME
.
More info here.
For Firestore
the aud
is https://firestore.googleapis.com/google.firestore.v1.Firestore
.
Cloudflare Workers expose the crypto
global for the Web Crypto API
.
const { getTokenFromGCPServiceAccount } = require('@sagi.io/workers-jwt')
const serviceAccountJSON = await ENVIRONMENT.get('SERVICE_ACCOUNT_JSON','json')
const aud = `https://firestore.googleapis.com/google.firestore.v1.Firestore`
const token = await getTokenFromGCPServiceAccount({ serviceAccountJSON, aud} )
const headers = { Authorization: `Bearer ${token}` }
const projectId = 'example-project'
const collection = 'exampleCol'
const document = 'exampleDoc'
const docUrl =
`https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents`
+ `/${collection}/${document}`
const response = await fetch(docUrl, { headers })
const documentObj = await response.json()
We use the node-webcrypto-ossl
package to imitate the Web Crypto API
in Node.
const { Crytpo }= require('node-webcrypto-ossl');
const cryptoImpl = new Crypto();
const { getTokenFromGCPServiceAccount } = require('@sagi.io/workers-jwt')
const serviceAccountJSON = { ... }
const aud = `https://firestore.googleapis.com/google.firestore.v1.Firestore`
const token = await getTokenFromGCPServiceAccount({ serviceAccountJSON, aud, cryptoImpl } )
<... SAME AS CLOUDFLARE WORKERS ...>
Node 15 introduces the Web Crypto API. When using NextJS, you may need to pass in the native Node webcrypto
lib to get both SSR and webpack to work during dev mode.
const { getTokenFromGCPServiceAccount } = require('@sagi.io/workers-jwt')
const serviceAccountJSON = { ... }
const aud = 'https://firestore.googleapis.com/google.firestore.v1.Firestore';
const token = await getTokenFromGCPServiceAccount({
serviceAccountJSON,
aud,
cryptoImpl: globalThis.crypto || require('crypto').webcrypto,
});
<... SAME AS CLOUDFLARE WORKERS ...>