When connecting to external oAuth2 services, one typically needs to generate and sign a JWT (JSON Web Token). This JWT then becomes part of an HTTP request to an external endpoint, aiming to secure an access token for subsequent interactions.
While JWTs can be symmetrically or asymmetrically signed, this article will focus on using RSA (asymmetric) for the signing process, rather than HMAC (symmetric).
In the context of JWT, an HMAC algorithm would use the same secret key to both create the signature for the JWT and to verify it later. Common HMAC algorithms for JWTs include HS256, HS384, and HS512 (where the numbers refer to the bit length of the hash).
On the other hand, an RSA algorithm would use the private key to sign the JWT, and the corresponding public key to verify it. Common RSA algorithms for JWTs include RS256, RS384, and RS512.
Setup
The first step is to generate a certificate that will have to be uploaded to NetSuite. The certificate can be generated using the openssl command and in most cases, a private key is provided by the service we want to connect to, so we can use it to sign our JWT.
The point is that in order to be able to use this key in an asymmetric signature, we need to embedded into a certificate.
Here are the certificate file types currently accepted by NetSuite:
- PFX
- P12
- PEM
Therefore, assuming we have a private key (PEM file), we can generate our certificate as follows:
openssl req -key {private_key_file} -new -x509 -days 365 -out mycert.crt
Once the certificate has been generated, you should have a CRT file and a Private Key file. If you open either files, you will find:
--------- BEGIN CERTIFICATE --------- <content> --------- END CERTIFICATE --------- --------- BEGIN PRIVATE KEY --------- <private key> --------- END PRIVATE KEY ---------
Then, you should create a PEM file that has a union of the content of the CRT and Private Key PEM files.
Finally, upload this certificate to NetSuite.
Steps
- Go to Setup -> Company -> Preferences -> Certificates
- At the top of the page click Create New
- In the New Certificate window, on the Details tab, enter a descriptive name for this certificate in the name field.
- In the ID field, enter a script ID you choose for this certificate. The script ID lets you have access to the certificate via SuiteScript.
- In the Description field, enter a description of the certificate
- In the Files tab, in the Certificate File field, choose the certificate file you have just generated
- In the Password field enter a password for the certificate. This value should have been provided by the certificate creator and it could be empty.
- On the Audience tab, you can restrict access to this certificate to only certain employees or scripts.
- In the Subsidiaries field select which subsidiaries this certificate applies to.
- Click Save
JWT Generation
Now we are ready to generate our JWT. The JWT is composed by 2 sections: Header and Payload.
Header
{
"alg": "RS256",
"typ": "JWT"
}
alg: determines the signing algorithm: RSA 256 in this case
type: determines the type of token
Payload
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
This section usually has user data and token expiration timestamps but it could also include aud to determine the audience. But this value or any other is usually provided by service we would like to connect to.
JWT is ultimately a dotted separated string composed by the Base64 representation of the header, the Base64 representation of the payload and the signature. It should look like this:
<strong>eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9</strong>.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.<strong>SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c</strong>
Signature
Let’s see how to generate a JWT and sign it using the certificate we have previously uploaded.
Here are the modules that need to be included as dependencies:
define(['N/encode', 'N/https', 'N/crypto/certificate'], (encode, https, certificate)
Then we need to define a variable with a reference to the ID of our certificate
const certId = "custcertificate_mycert";
After this, we must create and encode the JWT header and payload section as follows:
const header = encode
.convert({
string: JSON.stringify({
type: "JWT",
alg: "RS256",
}),
inputEncoding: encode.Encoding.UTF_8,
outputEncoding: encode.Encoding.BASE_64_URL_SAFE,
})
.replace(/=+$/, "");
const payload = encode
.convert({
string: JSON.stringify({
aud: "<value provided="" by="" service="">",
exp: Math.round(new Date().getTime() / 1000) + 60 * 60,
iat: Math.round(new Date().getTime() / 1000),
}),
inputEncoding: encode.Encoding.UTF_8,
outputEncoding: encode.Encoding.BASE_64_URL_SAFE,
})
.replace(/=+$/, "");</value>
In this example we are using the aud optional claim in the payload and it is important to notice that header and payload must be BASE_64_URL_SAFE encoded, using the N/encode module. Unlike simple BASE_64 encoding, BASE_64_URL_SAFE replaces ‘+’ character with ‘-‘, and ‘/’ characters with ‘__’.
In addition, ‘=’ characters should also be removed.
Once we have the header and payload, we must sign the concatenated strings with the certificate we have.
const mySigner = certificate.createSigner({
certId: certId,
algorithm: certificate.HashAlg.SHA256,
});
mySigner.update(header + "." + payload);
Finally, we must also encode and clean the signature and attach it to the previous concatenated strings.
const mySignature = mySigner
.sign({
outputEncoding: encode.Encoding.BASE_64_URL_SAFE,
})
.replace(/=+$/, "");
const JWT = `${header}.${payload}.${mySignature}`;
Sending a POST request with the JWT
Once the puzzle is complete we are ready to create a POST request adding our JWT to the body.
const body = `{"grant_type":"urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion":"${JWT}"}`;
const response = https.post({
url: "<token request="" endpoint="">",
body,
headers: {
Authorization: "Bearer " + JWT,
"Content-Type": "application/json",
},
});</token>
You should then receive a JSON response with a token that should be used for subsequent requests.