Validating Auth0 M2M Tokens with Mulesoft

Nathanael Guirguis
Revel Systems Engineering Blog
10 min readDec 12, 2022

At Revel Systems, we leverage Mulesoft to build production-ready integrations quickly for our internal systems. I am writing this article because recently I decided to use M2M authorization/authentication provided by Auth0 for our Mulesoft services and did not find any resources dedicated to this topic.

I have tried my best to explain things in as much detail as possible, however, I did need to assume some prior Mulesoft and Auth0 knowledge as I could have easily doubled the size of this article by going down some rabbit holes. Hopefully, this article will save you some time if this is something you also have wanted to get set up.

Setting up Auth0

To get started you will need to create an Auth0 Application as well as an API. For naming the API in Auth0, I simply used the URL of the Mulesoft service that will be validating the token. Once those are set up you can enable access from the Auth0 API to the Application by enabling it in the “Machine to Machine Applications” on the API menu.

Now that access to the application is enabled through the API, a POST request can be sent to Auth0 to retrieve a token using the API link as the audience. Instructions for how to send this request can be found here. Just make sure you are using the correct audience pointing to the API we are using.

So now we have an Application and API registered in Auth0 and can successfully retrieve tokens. But how can we verify these tokens before allowing the service access?

Setting up Mulesoft

Because Mulesoft does not have a connector for Auth0, the easiest way to create a token validator in Mulesoft is to create a custom Java class that uses the Auth0 Java SDK. In order to do this first make sure to add the Java module to your Mule project.

The next step will be to create your endpoint where this flow will execute in Anypoint Studio. The flow itself is fairly simple, for my implementation I used a try block to catch and propagate any errors the invoke static step detects.

Mulesoft Validate Token Basic Flow

Now let’s create our Java class and invoke it with the ‘invoke static’ connector in the image above. First, create a new package by clicking File -> New -> Java Package. Make sure the source folder is pointed towards project-name/src/main/java.

Next, we can add a Java class to our package by clicking on File -> New -> Java Class. Leaving all the defaults is fine simply add a name and click finish.

First, let's add the Java dependency to our pom.xml. Open the pom.xml and paste this into your dependencies (versions may be different just use the most recent version if possible):

  <dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.2.1</version>
</dependency>

Now for some code to add to our class. I used the readme from this repo published by Auth0 in order to write this class.

package com.revel.validate;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.concurrent.TimeUnit;

import com.auth0.jwk.InvalidPublicKeyException;
import com.auth0.jwk.JwkException;
import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.JwkProviderBuilder;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import com.auth0.jwt.interfaces.RSAKeyProvider;


public class Validate {

public static String validateJwt(String token, String issuer, String audience) {

DecodedJWT decodedJWT;

//create a JWK provider to pull RSA keys
final JwkProvider provider = new JwkProviderBuilder(issuer)
.cached(10, 24, TimeUnit.HOURS)
.rateLimited(10, 1, TimeUnit.MINUTES)
.build();
//returning private keys as null as they are not required to validate a JWT but
//still required to implement private key functions as part of the RSAKeyProvider interface
final RSAPrivateKey privateKey = null;
final String privateKeyId = null;
//implement RSAKeyProvider interface
RSAKeyProvider keyProvider = new RSAKeyProvider() {
@Override
public RSAPublicKey getPublicKeyById(String kid) {
try {
return (RSAPublicKey) provider.get(kid).getPublicKey();
} catch (InvalidPublicKeyException e) {
System.out.println(e);
e.printStackTrace();
} catch (JwkException e) {
System.out.println(e);
e.printStackTrace();
}
return null;
}
@Override
public RSAPrivateKey getPrivateKey() {
return privateKey;
}
@Override
public String getPrivateKeyId() {
return privateKeyId;
}
};

//verify JWT using keyProvider
try {
Algorithm algorithm = Algorithm.RSA256(keyProvider);
JWTVerifier verifier = JWT.require(algorithm)
//could add claim checks here with .withClaim()
.withIssuer(issuer)
.build();
decodedJWT = verifier.verify(token);

} catch (JWTVerificationException e) {
System.out.println(e);
throw e;
}
return privateKeyId;
}

}

In this class, I defined one method I named validateJwt that takes three arguments, the actual token provided by Auth0, the issuer, and the audience. In order to pass these variables into the function we will need to define some args in our ‘invoke static’ connector.

But first a couple of notes about the code above:

  • Occasionally Mulesoft has some particular ways that it needs to work or else you may get some unexpected results. While I was testing this I noticed defining a void function (that does not return any value at the end) makes it significantly harder to propagate an error from the Java class to the Mule error handler. So I have the method returning a string of the privateKeyId. This value is being returned by the class but is not passing back to the original Mule flow. What it will catch is any errors that pop up and will be able to be propagated from the try scope.
  • As noted in the code you will need to implement the RSAKeyProvider interface which requires three functions: getPrivateKey(), getPrivateKeyId, and getPublicKeyById. The main function we need to work for this functionality is the getPublicKeyById. This function will use the issuer variable to retrieve the public key for the token. For this reason, I did not fully implement all the functions and just created them non-functionally to satisfy the interface requirements. The private key would be required to create and sign tokens from your own service, but for validation, all that is needed is the public key.
  • I added one check to the JWT as an example, .withIssuer(). This function takes the issuer (I will explain the issuer further down) as an argument and validates that this token was indeed created from the issuer specified. Any JWT passed to this function that does not have the correct issuer will be denied access.

Now that we have our Java class, let’s link it up to our Mule flow! A Java class inside a Mule flow expects arguments to be passed to it to be named as ‘arg0, arg1…’ etc. These will line up with the arguments passed to the function respectively so make sure to put them in the correct order. Here is my dataweave script inside of the Invoke Static connector:

%dw 2.0
output application/java
---
{
arg0: attributes.headers.authorization,
arg1: "${validateConfig.issuer}",
arg2: "${validateConfig.audience}"
}

From this script, my Mule service is expecting there to be a header called authorization that contains the JWT. I did not implement logic to handle the “Bearer” prefix in my code example so for the value simply paste the token and nothing else.

The next two variables are more environment based, so these I put into configuration files named edge.yaml, stable.yaml, and prod.yaml. The issuer is the machine that is generating the token. This is going to be defined as your Auth0 tenant URL where your application and API live. The URL is https://{your-tenant-name}.auth0.com/.

The audience is the service or set of services that the requestor is trying to access. This will be the URL that was set for the API when registering it in Auth0. This name can also be found by going to the API page in Auth0 and near the top, there is a field named Identifier.

For the last step of setting up the Invoke Static connector we must point it to the Class we created as well as the method inside that class. This part can be a little confusing in Anypoint Studio as it tries to autofill values but in my experience is inconsistent at populating the list. You may get some errors displaying in Anypoint Studio saying the method value is invalid, this is okay the service will still run.

Enter the package and class name in the class field. For the method field it requires the method signature, this field particularly does not work well in Anypoint Studio so manually typing it in is the best option. As you can see from the picture I am getting an error on that field but it works as expected.

Mule File Config

Now that we have set up our Mule flow, and our Java class, and linked the two together, we need to make sure our Mule service knows to export this package when it is building. To do this open the mule-artefact.json file for the project and look for the classLoaderModelLoaderDescriptor object. Inside there should be an attributes object that contains an exportedPackages array. Add the package name to this array.

If your mule-artifact.json does not contain these you can paste this example from the Mulesoft website. Below I modified this example to include the exported Java package:

{
"configs": [
"auth0-token-validate-example.xml"
],
"redeploymentEnabled": true,
"name": "auth0-token-validate-example",
"minMuleVersion": "4.4.0",
"requiredProduct": "MULE_EE",
"classLoaderModelLoaderDescriptor": {
"id": "mule",
"attributes": {
"exportedResources": []
"exportedPackages": ["com.myCompany.validate"]
}
},
"bundleDescriptorLoader": {
"id": "mule",
"attributes": {}
}
}

Make sure the configs array contains all Mule project XML files (the one with the blue Mule icon next to them, in many cases this will be one file only.), and make sure the name is the same as the main XML file that is the entrypoint for the Mule service.

Now when we build our Mule service it will also build our package and be able to make calls to our class.

Error Handling

Our Java class is set to printing out any errors to the console, note that this is the debug console so won’t print to the Mule console when deployed, for that we need to handle the error in our flow. The error can be accessed through the Mule error object. On the error handling side of the try scope, add an “On Error Propagate” handler, inside of this we can place a transform message block with the dataweave script:

%dw 2.0
output application/json
---
{
"error": error.exception.cause.target.detailMessage
}

Here is an example flow showing error handling inside of Anypoint Studio:

Error handling for try scope

This will propagate the error up to any parent flows and pass the error message to the requestor.

I like to also use the transform message block to change the status code of the response. Any errors in token validation should return a status of 401. To do this we can simply click on the connector, then where the dataweave script goes there is a box above describing the target for that data transformation.

Right now the target is set for Payload and contains the logic for our error message. We can add another target, a Mulesoft default variable: httpStatus.

Hit the plus sign next to Payload and select add variable, and name the variable httpStatus.

For the output, we only need to set a number, 401 in our case.

Testing our Endpoint

In order to get a valid token we must call Auth0’s API. The base URL for this request will be https://{tenant-name}.auth0.com as used previously for the issuer. Auth0 has instructions on how to retrieve a token here.

Client_id and client_secret can be found on the Auth0 page for the application we created earlier. The audience will be the identifier for the Auth0 API we defined earlier. Auth0 will use this information to sign a token that will be valid for the specified period (token expiration can be changed in Auth0 settings).

As long as the information sent is correct, Auth0 will respond with an access_token, as well as information on the token type and when it expires. This access token is what we need to put in the header to the request for our Mule service to validate.

Make sure your Mule service is running locally, then make a request to the endpoint created with the authorization header containing the access token (AKA JWT). If no error pops up from the Invoke Static connector then the validation was successful with no errors.

Congratulations! You now have a Mulesoft Auth0 M2M Validation service! You may test this functionality by trying to retrieve a token with the wrong audience or client_id/secret pair, or by submitting expired tokens, or invalid tokens.

Thanks for reading!

--

--