Creating JWT Using SuiteScript 2.x for DocuSign API Integration

Solution for Creating JWT Using SuiteScript 2.x for DocuSign API Integration
is Given Below:

I’m updating a NetSuite integration for DocuSign to convert all scripting to SuiteScript 2.0 and update the authentication to utilize the JWT grant method, seeing as the current method looks like it will be deprecated for new applications in a couple weeks, and completely some time next year. I do have a couple questions about it all, though, that I’m having trouble answering.

For starters, when setting the IAT and EXP values for the body, am I getting this value based on the current server time (that varies depending on which time zone the account’s datacenter happens to reside), or should it be based on getting the UTC time instead? The documentation didn’t seem clear enough to me on that matter.

Additionally, I am unclear how to go about creating the signature. Retrieving and preparing the necessary data, aside form the aforementioned issue, is not a problem. But, arranging the data to be encoded based on what I’ve seen available within the 2.0 modules as compared to examples I’ve seen for creating the signature don’t seem to align so well. For example, this is an approximation of what I have seen:

var header = { typ : "JWT" , alg : "RS256" };
var body = { iss : "abc123" , sub : "def456" , iat : 123456789 , exp : 123456789 + (60 * 60) , scope : "signature" };
var encHeader = base64UrlEncode(JSON.stringify(header));
var encBody = base64UrlEncode(JSON.stringify(body));

// the key pairs are stored elsewhere, and the functions represent a way to retrieve the key contents
var publickKey = retrievePublicKey();
var privateKey = retrievePrivateKey();
var signature = RS256Encode(
    encHeader + "." + encBody ,
    publicKey ,
    privateKey
);

The examples show different encryption functions that all take in three arguments in same way demonstrated above. However, when looking at what is available within the native SuiteScript 2.0 modules, I don’t see anything quite comparable. I see that the crypto module (which gets pulled into other modules it seems) does have the ability to perform the required RSA SHA256 encoding, but not quite sure what the deal is to make sure that all the pieces are taken in together correctly.

I’ve taken a look at the JavaScript libraries for JWT creation at https://jwt.io/, but I have no experience with attempting to incorporate node modules into a SuiteScript project, if it’s even possible.

So, is there a way to natively construct a JWT within SuiteScript, or am I going to have to find a way to be able to reference a node module in a script?

— EDIT —

Ok, so it looks like I can use “Asynchronous Module Definition” (AMD) to import modules into a script by creating a JSON config file and adding the following to a script file’s JSDoc header:

*@NAmdConfig  /path/to/myModule.json

I figure that since I need it to be a relative path given that this project will be distributed to other accounts, something like this ought to work if the JSON config file is in the same directory:

*@NAmdConfig ./nodeModules.json

But I’m having a heck of a time determining exactly how to setup the JSON config. I can’t quite seem to find anything that really helps determine how to build it correctly. The biggest issue is determining whether or not a module is AMD or non-AMD, and in the case of non-AMD whether to attempt to import the scripts under the CJS section or the ESM section.

For any additional reference, I used NPM to install the jose module since it’s compatible with JavaScript/Node and has the encryption methods I need.

npm install jose

Right now, the path for it in the source project looks something like this:

SuiteScripts/Project_Name/lib/node_modules/jose

And the script file that will be referencing the module is located under this path:

SuiteScripts/Project_Name/lib

/**
 *@NApiVersion 2.x
 */

 define(['N/encode', 'N/crypto/certificate'], function(encode, cert){

   function signIt(payload, ttl){


      if(typeof payload.exp == 'undefined'){
         var secondsSinceEpoch = Math.round(Date.now() / 1000);
         var expAt = secondsSinceEpoch + (ttl || 60); 
         payload["exp"] = expAt;
         payload["iat"] = secondsSinceEpoch;
      }

      log.debug({
         title:'payload',
         details: JSON.stringify(payload)});

      var header = encode.convert({
         string: JSON.stringify({
            type:'JWT',
            alg:'RS256'
         }),
         inputEncoding: encode.Encoding.UTF_8,
         outputEncoding: encode.Encoding.BASE_64_URL_SAFE
      }).replace(/=+$/, '');

      var body = encode.convert({
         string: JSON.stringify(payload),
         inputEncoding: encode.Encoding.UTF_8,
         outputEncoding: encode.Encoding.BASE_64_URL_SAFE}).replace(/=+$/, '');

      var signer = cert.createSigner({
         certId:'custcertificate_sample', //from Setup -> Company -> Certificates
         algorithm: cert.HashAlg.SHA256
      });

      signer.update(header +'.'+ body);

      var sig = signer.sign({
         outputEncoding:encode.Encoding.BASE_64_URL_SAFE
      }).replace(/=+$/, '');

      return [header, body, sig].join('.');
   }

   return {
      signIt: signIt
   }
});

The uploaded key was generated like:

openssl genrsa -out private.pem 4096
openssl rsa -in private.pem -out public.pem -pubout
openssl req -key private.pem -new -x509 -days 3650 -subj "/C=CA/ST=Courtenay/O=Rule of Tech/OU=Information unit/CN=jwt.kotn.com" -out cert.pem

and then I just used a text editor to concatenate the cert and private key from cert.pem and private.pem:

-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----