Join the #BuildWithBuildAgent Challenge! Get recognized, earn exclusive swag, and inspire the ServiceNow Community with what you can build using Build Agent.  Join the Challenge.

P-Rudenko-SN
ServiceNow Employee
ServiceNow Employee

Introduction
In some integrations, the authentication flow requires generating a JSON Web Token (JWT) before requesting the actual access token. Instead of directly exchanging the client_id and client_secret with the authentication endpoint, the system expects a signed JWT that includes specific claims such as issuer, subject, timestamps, and other identifiers. In these scenarios, it may be necessary to implement a custom script in ServiceNow that constructs the JWT according to the provider’s requirements using values like clientId, clientSecret, and additional claims. Below is an outline of how this can be implemented.


1: Generate the JWT

In ServiceNow we create a Script Include which accepts inputs like client_id, client_secret, subject, and other claims. It then builds a JWT with the correct claims (iss, sub, iat etc.), signs it (HS256 in our case), and returns the token.

 

var JWTGenerator = Class.create();
JWTGenerator.prototype = {

    initialize: function () {},

    /**
     * params = {
     *   clientId: "",        // iss
     *   subject:  "",  // sub
     *   issuedAt: <GlideDateTime>,   
     *   secret:   "<CLIENT_SECRET>",
     *   user:     ""    
     * }
     */
    generate: function (params) {
        var now = new GlideDateTime();
        var issuedAt = params.issuedAt || now;

        // ----- HEADER (order: typ, alg) -----
        var headerObj = {};
        headerObj.typ = "JWT";
        headerObj.alg = "HS256";

        var headerJson = JSON.stringify(headerObj);
        var encodedHeader = this._base64UrlSafe(this._b64EncodeString(headerJson));

        // ----- PAYLOAD (order: sub, iss, iat, user) -----
        var payloadObj = {};
        payloadObj.sub = params.subject;
        payloadObj.iss = params.clientId;
        payloadObj.iat = this._toEpochSeconds(issuedAt);

        if (params.user)
            payloadObj.user = params.user;

        var payloadJson = JSON.stringify(payloadObj);
        var encodedPayload = this._base64UrlSafe(this._b64EncodeString(payloadJson));

        var unsignedToken = encodedHeader + "." + encodedPayload;

        // ----- SIGNATURE (HS256) -----
        var mac = new global.CertificateEncryption();

        // Key must be base64 so HMAC uses the same raw bytes as the Java/Auth0 code
        var keyBase64 = this._b64EncodeString(params.secret);

        var signatureBase64 = mac.generateMac(
            keyBase64,
            "HmacSHA256",
            unsignedToken
        );

        var encodedSignature = this._base64UrlSafe(signatureBase64);

        return unsignedToken + "." + encodedSignature;
    },

    // ---------- helpers ----------

    _b64EncodeString: function (str) {
        try {
            if (typeof GlideStringUtil !== 'undefined' &&
                GlideStringUtil.base64Encode) {
                return GlideStringUtil.base64Encode(str);
            }
        } catch (e) {
            // ignore and fall back to gs
        }
        return gs.base64Encode(str);
    },

    _toEpochSeconds: function (gdt) {
        return Math.floor(gdt.getNumericValue() / 1000);
    },

    _base64UrlSafe: function (b64) {
        b64 = b64.split("=").join("");   // remove padding
        b64 = b64.split("+").join("-");  // + -> -
        b64 = b64.split("/").join("_");  // / -> _
        return b64;
    },

    type: "JWTGenerator"
};

 

Important notes:

  • The CertificateEncryption() API is used to ensure the solution works in the custom scope.

  • The claims must match exactly what the authentication API expects.

Now the script include above can be called from the Action script in the Workflow Studio/ Flow Designer, expecting the userId as the action input:

 

(function execute(inputs, outputs) {

    var now = new GlideDateTime();

    var params = {

        clientId: gs.getProperty({sysPropertyNameClientId}),        // sys_property of "Password2" type
        subject: {custom_value} + inputs.user + '@' + inputs.user,
        issuedAt: now,
        secret: gs.getProperty({sysPropertyNameClientSecret}),  // sys_property of "Password2" type
        user: inputs.user
    };

    var jwt = new x_{custom_app_name}.JWTGenerator.generate(params);

    // Return the JWT to the Action outputs
    outputs.jwt_token = jwt;

})(inputs, outputs);

2: Send the JWT to Obtain the Access Token

Once you have the JWT, the next step is to call the authentication endpoint and submit the token in the required header or body parameter. The API then returns your access token, which you use for subsequent calls.

 

Placeholder – REST call + headers:

POST https://example.com/oauth2/token
Headers:
  Content-Type: application/x-www-form-urlencoded
  Authorization: Bearer <your_jwt_token>

Once the response is received from the token API, you'll need to parse the response body JSON to get the required access token.

 

At the end, the Flow Designer action would contain the following steps:

  1. Call the JWT Script Include and generate the JWT.

  2. Call the REST message to retrieve the access token.

  3. Parse the REST message response and get the access token.

 

Validate the JWT Externally

During development, you might need to use tools like jwt.io to validate the generated JWT. This helps ensure that things like the proper algorithm (HS256/RS256) are used, claims are correct, and a valid signature is present.

 

Summary
This pattern of generating a JWT, exchanging it for an access token, then using that token in your integration works reliably and can be packaged as a reusable Flow Designer Action within ServiceNow. It’s a valuable approach when dealing with APIs that expect JWT bearer authentication.

Version history
Last update:
2 hours ago
Updated by:
Contributors