JWT Tokens and Google App Script

NP
2 min readSep 17, 2021

--

If your SDK makes network calls and you’re working to make your SDK available in Google App Script environment, the chances are that you need to obtain a JWT token.

If you are using jwt = require('jsonwebtoken') or a similar library in your SDK to obtain the assertion code, you’re going to encounter an error that the buffer type is undefined in GAS environment:

ReferenceError: Buffer is not defined

This likely because these libraries are doing buffering underneath the hood to work with your private key. How do you work around this problem? The short answer is you do need to implement the JWT signing and assertion logic by yourself using the GAS provided APIs — mainly the ComputeRSA* methods from: https://developers.google.com/apps-script/reference/utilities/utilities

Good news is, I’ll walk you through how to do this so you don’t have to start from scratch.

In order to get your assertion token, you need to create your header and payload and you need to follow that with its signature. If you’re not familiar with out a JWT is structured, there are many great resources for you to read like this one: https://jwt.io/introduction

Construct your header:

var JWT_SIGNING_ALGO = "RS256";const header = {
typ: 'JWT',
alg: JWT_SIGNING_ALGO,
};

Then, create the payload that you’ll be sending:

var MILLESECONDS_PER_SECOND = 1000;
var now = Math.floor(Date.now() / MILLESECONDS_PER_SECOND);
var later = now + expiresIn;
var parsedScopes = Array.isArray(scopes) ? scopes.join(' ') : scopes;
var jwtPayload = {
iss: clientId,
aud: oAuthBasePath,
iat: now,
exp: later,
scope: parsedScopes,
};

And now you need to sign ${Header}.${jwtPayload} with your private key. To do this, we will use Utilities.base64EncodeWebSafe to encode and Utilities.ComputeRSASha256Signature to sign the payload:

const base64Encode = (text, json = true) => {
const data = json ? JSON.stringify(text) : text;
return S(data).replace(/=+$/, '');
};
const toSign = `${base64Encode(header)}.${base64Encode(jwtPayload)}`const signatureBytes = Utilities.computeRsaSha256Signature(
toSign,
privateKey
);
const signature = base64Encode(signatureBytes, false);return `${toSign}.${signature}`;

There are few things to note about ComputeRSASha256Signature method:

  • It only accepts a Private key not a RSA one. If you pass a RSA private key to the method, you’re going to see an error like below:
Invalid argument: key

Look at the header in your key. If it starts with ` — — -BEGIN PRIVATE KEY — — -` then you’re good. But if you see ` — — -BEGIN RSA PRIVATE KEY — — -` then you need to convert your private key format from RSA. You can do so by running:

openssl pkcs8 -topk8 -inform pem -in private.pem -outform pem -nocrypt -out newPrivate.pem

Now you can pass the newPrivate.pem to ComputeRSASha256Signature and you should have your assertion. Once you have your assertion, the next step is to make a network call and get your token. In order to do that, please read my previous article on how to make a network call in GAS: https://nima-poulad-40744.medium.com/how-to-make-your-npm-package-available-in-google-app-script-92662e136985

Thank you for reading this far. I hope this article saved you some headache.

--

--

NP
NP

Written by NP

A tech enthusiast who resides in a jungle.

No responses yet