DataWeave modules provide reusable functions that aren't necessarily core to the runtime, like isAlpha, takeWhile, countBy, etc. For those newer to MuleSoft, developer written functions are typically embedded directly into the application. While this works in the short term, this has long term implications as we start to create functions we want to use in multiple applications. Copying the code into each application is not only a time consuming process, you're also left in a situation where each update to the functions requires updating each application.

The solution? Create modules! Once you create a DataWeave module, you can include them in your applications the same way you include connectors: as a Maven dependency. This gives us a single location from which to manage our DataWeave code, allowing us to introduce versioning, unit/integration testing, and reuse/discovery of our code.

What you'll learn

How to create, compile, and publish a DataWeave module

How to debug DataWeave modules, including using line-by-line breakpoints

How to include and use a previously created DataWeave module in a Mule application

What you'll need

Finished Project

https://github.com/mikeacjones/dw-jwt-module

Once you have IntelliJ installed, we need to add the DataWeave plugin.

  1. On the "Welcome to IntelliJ IDEA" initial screen, click on Plugins
  2. Go to the Marketplace tab
  3. Search for DataWeave
  4. Install "Mulesoft DataWeave 2.0 and RestSdk", published by MuleSoft

  1. In IntelliJ, click Create Project
  2. Select the type "Weave Project"

  1. Set groupId to com.mulesoft.codelabs
  2. Set artifactId to jwt-dw-module
  3. Leave the version as 1.0.0-SNAPSHOT

  1. On the next screen, leave all of the defaults

  1. Set the project name to jwt-dw-module and choose a location to save the files

  1. Create the project

Once the project has been created, you'll notice that a number of folders are present by default:

This is where we will set up our code, as well as any unit testing we wish to include. The project structure is dictated by the configuration in the POM file:

This entry indicates that, when building the project, we should look for DataWeave files in src/main/dw. Your folder structure here dictates what the import looks like; eg: creating a subfolder src/main/dw/jwt with a file called RSA.dwl will make our import: import * from dw::jwt::RSA.

When adding DataWeave files in a Mule Application, we typically place them in the default src/main/resources directory; however, for the purposes of a DataWeave module best practice suggests using src/main/dw.

These entries dictate where the build plugin should check for unit tests that need to be run.

Now that we have the project scaffolded, let's add our code.

Common.dwl: Functions shared by both the RSA and HMAC functions

  1. In src/main/dw, create a folder called jwt.
  2. In src/main/dw/jwt, create a Dataweave Component (module) called Common.dwl

  1. Paste the following code:

src/main/dw/jwt/Common.dwl

%dw 2.0
import toBase64 from dw::core::Binaries

fun binaryJson(obj: Object) =
    write(obj, 'application/json', { indent: false }) as Binary

fun base64URL(str: Binary) =
    toBase64(str) replace "+" with "-" replace "/" with "_" replace "=" with ""

fun base64Obj(obj: Object) =
    base64URL(binaryJson(obj))

/** basic JWT with header and payload, no signing */
fun JWT(header: Object, payload: Object) =
    "$(base64Obj(header)).$(base64Obj(payload))"

/** basic JWT with no user specified header */
fun JWT(payload: Object) =
    JWT({typ:'JWT'}, payload)

HMAC.dwl: Functions used for creating HMAC-SHA signed JSON Web Tokens

  1. In src/main/dw/jwt, create a Dataweave Component (module) called HMAC.dwl

  1. Paste the following code:

src/main/dw/jwt/HMAC.dwl

%dw 2.0
import HMACBinary from dw::Crypto
import fail from dw::Runtime
import jwt::Common

var algMapping = {
    "HmacSHA256": "HS256",
    "HmacSHA384": "HS384",
    "HmacSHA512": "HS512"
}

fun alg(algorithm: String) : String | Null =
    algMapping[algorithm] default fail('Invalid algorithm provided for signing')

fun signJWT(content: String, key: String, alg: String) : String =
    Common::base64URL(HMACBinary(key as Binary, content as Binary, alg))

/** JWT with header, payload, and signature by specific algorithm. valid algorithms dictated by HMACWith */
fun JWT(header: Object, payload: Object, signingKey: String, algorithm: String) : String = do {
    var jwt = Common::JWT({
        (header - "alg" - "typ"),
        alg: alg(algorithm),
        typ: "JWT"
    }, payload)
    ---
    "$(jwt).$(signJWT(jwt, signingKey, algorithm))"
}

/** JWT with header, payload, and signed with HMAC-SHA256*/
fun JWT(header: Object, payload: Object, signingKey: String) : String = do {
    JWT(header, payload, signingKey, 'HmacSHA256')
}

/** JWT with payload and automatically generated header, signed with HMAC-SHA256 */
fun JWT(payload: Object, signingKey: String) : String =
    JWT( {},payload, signingKey, 'HmacSHA256')

RSAHelper.java: Provides functionality not available in DataWeave via Java necessary for RSA signing

When creating DataWeave modules, we can even include and leverage custom Java code, or java dependencies! DataWeave does not currently have built in support for RSA signing, so we will create a simple helper java file to handle this for us.

  1. In src/main/java, create Java class RSAHelper.java
  2. Past the following code:

src/main/java/RSAHelper.java

import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

public class RSAHelper {
    public static String signString(String content, String privateKeyContent, String algorithm) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, UnsupportedEncodingException, SignatureException {
        KeyFactory kf = KeyFactory.getInstance("RSA");
        PKCS8EncodedKeySpec keySpecPKCS8 = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyContent));
        PrivateKey pk = kf.generatePrivate(keySpecPKCS8);

        Signature privateSignature = Signature.getInstance(algorithm);
        privateSignature.initSign(pk);
        privateSignature.update(content.getBytes("UTF-8"));
        byte[] s = privateSignature.sign();
        return Base64.getUrlEncoder().encodeToString(s).replace("=", "");
    }
}

RSA.dwl: Functions used for creating RSA signed JSON Web Tokens

  1. In src/main/dw/jwt, create a Dataweave Component (module) RSA.dwl

  1. Paste the following code:

src/main/dw/jwt/RSA.dwl

%dw 2.0
import java!RSAHelper
import jwt::Common
import fail from dw::Runtime

var algMapping = {
    "Sha256withRSA": "RS256",
    "Sha384withRSA": "RS384",
    "Sha512withRSA": "RS512"
}

fun alg(algorithm: String) : String | Null =
    algMapping[algorithm] default fail('Invalid algorithm provided for signing')

fun cleanKey(key: String) : String =
    key replace "\n" with "" replace /(-+)(BEGIN|END)(\sRSA)? (PRIVATE|PUBLIC) KEY(-+)/ with "" replace " " with ""

fun signJWT(jwt: String, privateKey: String, algorithm: String) : String =
    RSAHelper::signString(jwt, cleanKey(privateKey), algorithm)

/** JWT with header, payload, and signature by specific algorithm. valid algorithms dictated by HMACWith */
fun JWT(header: Object, payload: Object, pkcs8privateKey: String, algorithm: String) : String = do {
    var jwt = Common::JWT(
    { (header - "alg" - "typ"), alg: alg(algorithm), typ: 'JWT' },
    payload)
    ---
    "$(jwt).$(signJWT(jwt, pkcs8privateKey, algorithm))"
}

/** JWT with payload and automatically generated header, signed with HMAC-SHA256 */
fun JWT(payload: Object, pkcs8privateKey: String) : String =
    JWT({}, payload, pkcs8privateKey, "Sha256withRSA")

Setup a simple mapping test:

Next, in order to have an entry point for testing our code, let's set up a simple mapping.

  1. In src/test/dwtest, create a DataWeave Component (mapping) called HMAC_Testing.dwl

  1. At the bottom of IntelliJ, click Weave Preview to open the mapping preview. This shows us a live preview of the results of our dataweave!

  1. In order to simulate some incoming data, we'll add an input. This allows us to set a payload object with some JSON. Click the + button in the Inputs panel

  1. Set a sample payload which we will include in our token:
{
  "firstName": "Michael",
  "lastName": "Jones"
}
  1. And finally in the mapping DataWeave code, paste the following:
%dw 2.0
import jwt::HMAC
output application/json
---
HMAC::JWT(payload, "Mulesoft123!")

And there we go! We should now see a signed JWT being output in the preview pane. That tokens should look like "eyJhbGciOiAiSFMyNTYiLCJ0eXAiOiAiSldUIn0.eyJmaXJzdE5hbWUiOiAiTWljaGFlbCIsImxhc3ROYW1lIjogIkpvbmVzIn0.izgs2w_CIE-qa0TdvzFErTOW4TPSDWrZs4b7xXdChFk", assuming that you used the same input payload that I did. We can take this token and key, and use https://jwt.io in order to verify a valid token was created!

Now that we have our module wrapped up, lets go ahead and add a basic Unit Test and run the test lifecycle phase to see how it looks.

  1. In src/test/dwmit, create a new folder called HMAC_Payload_Only. This folder will contain a unit test against which we are testing calling HMAC::JWT with only a payload and key.
  2. In src/test/dwmit/HMAC_Payload_Only, create transform.dwl and paste the following code:
%dw 2.0
import jwt::HMAC
output application/json
---
HMAC::JWT({
            "firstName": "Michael",
            "lastName": "Jones"
          }, "Mulesoft123!")
  1. In src/test/dwmit/HMAC_Payload_Only, create out.json and paste the following:
"eyJhbGciOiAiSFMyNTYiLCJ0eXAiOiAiSldUIn0.eyJmaXJzdE5hbWUiOiAiTWljaGFlbCIsImxhc3ROYW1lIjogIkpvbmVzIn0.izgs2w_CIE-qa0TdvzFErTOW4TPSDWrZs4b7xXdChFk"
  1. Open terminal, and run mvn test; you should see your test show up as passing!

Now that you see the basic functionality, take some time to play around with the test in order to see failures, or create additional tests!

For this example, we will be using your Anypoint Exchange as a maven repository target.

  1. Find your organization ID:
  1. Log into https://anypoint.mulesoft.com
  2. Make sure you are in the master business group.

  1. Navigate to Access Management
  2. Click on your master business group and copy your Organization Id

  1. In your DataWeave project, remove the line dw-library in POM.xml
  2. In your DataWeave project, update your .. key to your Organization Id in POM.xml
  3. In your DataWeave project, update your POM.xml by adding a distributionManagement key
<distributionManagement>
  <repository>
    <id>exchange-repository</id>
    <url>https://maven.anypoint.mulesoft.com/api/v2/organizations/YOUR-ORG-ID-HERE/maven</url>
    <layout>default</layout>
  </repository>
</distributionManagement>
  1. Now that we have a maven target setup, run the following commands in terminal:
  1. mvn install (this install into our local repo; if you can't get your credentials setup for the Exchange, this will allow us to continue with the next section)
  2. mvn deploy (this will deploy to our exchange maven repo, assuming your credentials are setup)

There you go! We can now easily include this module in our Mule application simply by adding a dependency to our project's POM.xml file!

<dependency>
    <!-- Use your ORG ID if you changed it earlier -->
    <groupId>com.mulesoft.codelabs</groupId> 
    <artifactId>jwt-dw-module</artifactId>
    <version>1.0.0-SNAPSHOT</version>
<dependency>

And now you can include it in your transform:

%dw 2.0
import jwt::HMAC
output application/json
---
HMAC::JWT({
    name: 'Michael Jones'
}, 'Mulesoft123!')

And now we have a reusable library separate from our Mule applications, with its own Unit Testing and versioning!