Implement security using JSON Web Token (JKT) in MuleSoft 4.0

  • September 16, 2020

What is a JSON Web Token?

JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure. This enables the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.

Why JWT?

In today’s digital world of application programming interfaces (APIs), it is paramount to protect access to APIs to avoid data being leaked. The following features of JWT make a compelling case for using JWTs to authenticate and authorize access to APIs.

  • JWTs are stateless, making tokens easier to manage.
  • JWTs can be used to transfer claims securely between parties.
  • JWTs are scalable.
  • The payload of a token can be expanded to increase new claims easily.
  • JWTs are decoupled in nature allowing authentication to happen on a different server.
  • The tokens are compact. JSON format makes the token less verbose than XML. The smaller size allows easier transmission over HTTP.
  • JWTs are JSON-based and can be easily parsed by multiple receiving systems, especially mobiles. This enables an industry-wide adoption.

Note: JWT validation policies can be applied through the API Manager.

JWT using custom Java class

One of the ways you can create a JWT token and use it in Mule is by using a custom Java class.

We need to create the token in the Java class and can validate the same in other Java class.

1. Java JWT: JSON Web Token for Java and Android

Use your favorite Maven-compatible build tool to pull the dependency (and its transitive dependencies) from Maven Central:

Maven

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.6.0</version>
</dependency> 

Gradle

ependencies {
    compile 'io.jsonwebtoken:jjwt:0.6.0'
}

Note: JJWT depends on Jackson 2.x.

2. JSON token library

It depends on Google Guava. The library is in fact used by Google Wallet.

Here is how to create a jwt and how to verify it and deserialize it.

Maven

<dependency>
    <groupId>com.googlecode.jsontoken</groupId>
    <artifactId>jsontoken</artifactId>
    <version>1.0</version>
</dependency>
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
</dependency>

3. Generate a JWT

Here’s a Java function that generates a JWT taking userId and noOfDays as Input parameters.

Java function

/**
     * Creates a json web token which is a digitally signed token that contains a payload (e.g. userId to identify 
     * the user). The signing key is secret. That ensures that the token is authentic and has not been modified.
     * Using a jwt eliminates the need to store authentication session information in a database.
     * @param userId
     * @param durationDays
     * @return
     */
    public static String createJsonWebToken(String userId, Long durationDays)    {
        //Current time and signing algorithm
        Calendar cal = Calendar.getInstance();
        HmacSHA256Signer signer;
        try {
            signer = new HmacSHA256Signer(ISSUER, null, SIGNING_KEY.getBytes());
        } catch (InvalidKeyException e) {
            throw new RuntimeException(e);
        }
 
        //Configure JSON token
        JsonToken token = new net.oauth.jsontoken.JsonToken(signer);
        token.setAudience(AUDIENCE);
        token.setIssuedAt(new org.joda.time.Instant(cal.getTimeInMillis()));
        token.setExpiration(new org.joda.time.Instant(cal.getTimeInMillis() + 1000L * 60L * 60L * 24L * durationDays));
 
        //Configure request object, which provides information of the item
        JsonObject request = new JsonObject();
        request.addProperty("userId", userId);
 
        JsonObject payload = token.getPayloadAsJsonObject();
        payload.add("info", request);
 
        try {
            return token.serializeAndSign();
        } catch (SignatureException e) {
            throw new RuntimeException(e);
        }
    }

Final values

We have assumed some values to be final and static:

private static final String AUDIENCE = "NotReallyImportant"; 

    private static final String ISSUER = "YourCompanyNameHere"; 

    private static final String SIGNING_KEY = "LongAndHardToGuessValueWithSpecialCharacters@^($%*$%";

4. Verify a JWT token

Here’s a Java function that verifies a JWT token taking the token as input and decoding every encoded information from the token returning a TokenInfo object in the process.

Java function

/**
     * Verifies a json web token's validity and extracts the user id and other information from it. 
     * @param token
     * @return
     * @throws SignatureException
     * @throws InvalidKeyException
     */

    public static TokenInfo verifyToken(String token)  
    {
        try {
            final Verifier hmacVerifier = new HmacSHA256Verifier(SIGNING_KEY.getBytes());
 
            VerifierProvider hmacLocator = new VerifierProvider() {
 
                @Override
                public List<Verifier> findVerifier(String id, String key){
                    return Lists.newArrayList(hmacVerifier);
                }
            };
            VerifierProviders locators = new VerifierProviders();
            locators.setVerifierProvider(SignatureAlgorithm.HS256, hmacLocator);
            net.oauth.jsontoken.Checker checker = new net.oauth.jsontoken.Checker(){
 
                @Override
                public void check(JsonObject payload) throws SignatureException {
                    // don't throw - allow anything
                }
 
            };
            //Ignore Audience does not mean that the Signature is ignored
            JsonTokenParser parser = new JsonTokenParser(locators,
                    checker);
            JsonToken jt;
            try {
                jt = parser.verifyAndDeserialize(token);
            } catch (SignatureException e) {
                throw new RuntimeException(e);
            }
            JsonObject payload = jt.getPayloadAsJsonObject();
            TokenInfo t = new TokenInfo();
            String issuer = payload.getAsJsonPrimitive("iss").getAsString();
            String userIdString =  payload.getAsJsonObject("info").getAsJsonPrimitive("userId").getAsString();
            if (issuer.equals(ISSUER) )
            {
                t.setUserId(userIdString);
                t.setIssued(new DateTime(payload.getAsJsonPrimitive("iat").getAsLong()));
                t.setExpires(new DateTime(payload.getAsJsonPrimitive("exp").getAsLong()));
                return t;
            }
            else
            {
                return null;
            }
        } catch (InvalidKeyException e1) {
            throw new RuntimeException(e1);
        }
    }

Mule implementation of JWT-based authentication

Here we’ll write Java classes for generation and verification of JWT tokens for user authentication.

  • Creating Java packages and classes

Create a new Mule application and in the src/main/java folder create a package.

‘com.java.muleinuse’.

Inside the package we need to create 2 classes in 2 separate files viz.

Creating Java Packages and Classes

‘AuthHelper.java’ will harbor the functions to encode and decode JWT and ‘TokenInfo.java’ will denote the output of the decoding process of JWT tokens.

  • Exporting the Java package

In Mule 4, the Java package needs to be exported explicitly (mentioned as an exportedResource in muleArtifacts.json file) for the enablement of any Java functionality.

Exporting the Java Package

  • AuthHelper.java

This class contains methods of both generating as well as verifying the JWT.

import java.security.InvalidKeyException;
import java.security.SignatureException;
import java.util.Calendar;
import java.util.List;
 
import net.oauth.jsontoken.JsonToken;
import net.oauth.jsontoken.JsonTokenParser;
import net.oauth.jsontoken.crypto.HmacSHA256Signer;
import net.oauth.jsontoken.crypto.HmacSHA256Verifier;
import net.oauth.jsontoken.crypto.SignatureAlgorithm;
import net.oauth.jsontoken.crypto.Verifier;
import net.oauth.jsontoken.discovery.VerifierProvider;
import net.oauth.jsontoken.discovery.VerifierProviders;
 
import org.joda.time.DateTime;

import com.google.common.collect.Lists;
import com.google.gson.JsonObject;
 
 
/**
 * Provides static methods for creating and verifying access tokens and such. 
 * 
 *package com.java.muleinuse;


 */
public class AuthHelper {
 
    private static final String AUDIENCE = "NotReallyImportant";
 
    private static final String ISSUER = "ISN";
 
    private static final String SIGNING_KEY = "LongAndHardToGuessValueWithSpecialCharacters@^($%*$%";
 
    /**
     * Creates a json web token which is a digitally signed token that contains a payload (e.g. userId to identify 
     * the user). The signing key is secret. That ensures that the token is authentic and has not been modified.
     * Using a jwt eliminates the need to store authentication session information in a database.
     * @param userId
     * @param durationDays
     * @return
     */
    public static String createJsonWebToken(String userId, Long durationDays)    {
        //Current time and signing algorithm
        Calendar cal = Calendar.getInstance();
        HmacSHA256Signer signer;
        try {
            signer = new HmacSHA256Signer(ISSUER, null, SIGNING_KEY.getBytes());
        } catch (InvalidKeyException e) {
            throw new RuntimeException(e);
        }
 
        //Configure JSON token
        JsonToken token = new net.oauth.jsontoken.JsonToken(signer);
        token.setAudience(AUDIENCE);
        token.setIssuedAt(new org.joda.time.Instant(cal.getTimeInMillis()));
        token.setExpiration(new org.joda.time.Instant(cal.getTimeInMillis() + 1000L * 60L * 60L * 24L * durationDays));
 
        //Configure request object, which provides information of the item
        JsonObject request = new JsonObject();
        request.addProperty("userId", userId);
 
        JsonObject payload = token.getPayloadAsJsonObject();
        payload.add("info", request);
 
        try {
            return token.serializeAndSign();
        } catch (SignatureException e) {
            throw new RuntimeException(e);
        }
    }
 
    /**
     * Verifies a json web token's validity and extracts the user id and other information from it. 
     * @param token
     * @return
     * @throws SignatureException
     * @throws InvalidKeyException
     */
    public static TokenInfo verifyToken(String token)  
    {
        try {
            final Verifier hmacVerifier = new HmacSHA256Verifier(SIGNING_KEY.getBytes());
 
            VerifierProvider hmacLocator = new VerifierProvider() {
 
                @Override
                public List<Verifier> findVerifier(String id, String key){
                    return Lists.newArrayList(hmacVerifier);
                }
            };
            VerifierProviders locators = new VerifierProviders();
            locators.setVerifierProvider(SignatureAlgorithm.HS256, hmacLocator);
            net.oauth.jsontoken.Checker checker = new net.oauth.jsontoken.Checker(){
 
                @Override
                public void check(JsonObject payload) throws SignatureException {
                    // don't throw - allow anything
                }
 
            };
            //Ignore Audience does not mean that the Signature is ignored
            JsonTokenParser parser = new JsonTokenParser(locators,
                    checker);
            JsonToken jt;
            try {
                jt = parser.verifyAndDeserialize(token);
            } catch (SignatureException e) {
                throw new RuntimeException(e);
            }
            JsonObject payload = jt.getPayloadAsJsonObject();
            TokenInfo t = new TokenInfo();
            String issuer = payload.getAsJsonPrimitive("iss").getAsString();
            String userIdString =  payload.getAsJsonObject("info").getAsJsonPrimitive("userId").getAsString();
            if (issuer.equals(ISSUER) )
            {
                t.setUserId(userIdString);
                t.setIssued(new DateTime(payload.getAsJsonPrimitive("iat").getAsLong()));
                t.setExpires(new DateTime(payload.getAsJsonPrimitive("exp").getAsLong()));
                return t;
            }
            else
            {
                return null;
            }
        } catch (InvalidKeyException e1) {
            throw new RuntimeException(e1);
        }
    }
 
 
}

AuthHelper.java

Note: The libraries could be imported from the Mule palette.

It needs some of the MongoDb and OAuth dependencies among others for its proper implementation.

  • TokenInfo.java

This class defines the structure in which the output will be shown once the JWT is decoded.

The verifyToken() returns a TokenInfo object.

package com.java.muleinuse;

import org.joda.time.DateTime;

public class TokenInfo {
    private String userId;
    private DateTime issued;
    private DateTime expires;
    public String getUserId() {
        return userId;
    }
    public void setUserId(String userId) {
        this.userId = userId;
    }
    public DateTime getIssued() {
        return issued;
    }
    public void setIssued(DateTime issued) {
        this.issued = issued;
    }
    public DateTime getExpires() {
        return expires;
    }
    public void setExpires(DateTime expires) {
        this.expires = expires;
    }
}

5. Mule Flows

We’ll create 3 flows: one for generation of token , one for its verification and the last one where the actual logic goes after verification.

The actual logic Flow will only be triggered if and only if the verification of the token takes place successfully.

Mule Flows

Fig: A mule application containing the generateToken, verifyToken and implementationOfRealLogicAfterVerification Flows.

generateTokenFlow

This flow contains a transform message wherein the userId is taken from the queryParams and the noOfDays from the property file for input to the static Java function createJsonWebToken(String userId, Long durationDays) which in invoked through the Invoke Static processor.

generateTokenFlow with Mule 4

Fig: generateTokenFlow with Mule 4

Details

Details of generateTokenFlow with Mule 4

Details of generateTokenFlow with Mule 4

Details of generateTokenFlow with Mule 4

Details of generateTokenFlow with Mule 4

Details of generateTokenFlow with Mule 4

Details of generateTokenFlow with Mule 4

verifyTokenFlow

This flow takes a payload containing a JWT token as input and returns the decoded details as output using the verifyToken(String token) method of the AuthHelper.java class invoked through Invoke Static processor.

verifyTokenFlow with Mule 4

Fig: verifyTokenFlow with Mule 4

Details

Details of verifyTokenFlow with Mule 4

Details of verifyTokenFlow with Mule 4

Details of verifyTokenFlow with Mule 4

Details of verifyTokenFlow with Mule 4

Details of verifyTokenFlow with Mule 4

Details of verifyTokenFlow with Mule 4

Sample requests responses

generateToken

Request

Url: http://localhost:8093/generateToken?userId=YourUserId

Response

eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJJU04iLCJhdWQiOiJOb3RSZWFsbHlJbXBvcnRhbnQiLCJpYXQiOjE1OTU4MzQxNTMsImV4cCI6MTU5NjY5ODE1MywiaW5mbyI6eyJ1c2VySWQiOiJTaHViaGFtIn19.0ucbeW32P0aNZ2DmUTDhcHybOHgLVsQjeSdZT87olus

verifyToken

Request

Url: http://localhost:8093/verify

Request body:

{
    "token": "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJJU04iLCJhdWQiOiJOb3RSZWFsbHlJbXBvcnRhbnQiLCJpYXQiOjE1OTU4MzQxNTMsImV4cCI6MTU5NjY5ODE1MywiaW5mbyI6eyJ1c2VySWQiOiJTaHViaGFtIn19.0ucbeW32P0aNZ2DmUTDhcHybOHgLVsQjeSdZT87olus"
}

Response

{
  "expires": {
    "dayOfYear": 19,
    "year": 1970,
    "weekyear": 1970,
    "chronology": {
      "zone": {
        "uncachedZone": {
          "fixed": false,
          "cachable": true,
          "ID": "Asia/Kolkata"
        },
        "fixed": false,
        "ID": "Asia/Kolkata"
      }
    },
    "weekOfWeekyear": 4,
    "secondOfMinute": 38,
    "millisOfDay": 61298153,
    "monthOfYear": 1,
    "dayOfWeek": 1,
    "minuteOfDay": 1021,
    "era": 1,
    "zone": {
      "uncachedZone": {
        "fixed": false,
        "cachable": true,
        "ID": "Asia/Kolkata"
      },
      "fixed": false,
      "ID": "Asia/Kolkata"
    },
    "yearOfCentury": 70,
    "secondOfDay": 61298,
    "millisOfSecond": 153,
    "afterNow": false,
    "equalNow": false,
    "beforeNow": true,
    "dayOfMonth": 19,
    "hourOfDay": 17,
    "centuryOfEra": 19,
    "millis": 1596698153,
    "yearOfEra": 1970,
    "minuteOfHour": 1
  },
  "userId": "Shubham",
  "issued": {
    "dayOfYear": 19,
    "year": 1970,
    "weekyear": 1970,
    "chronology": {
      "zone": {
        "uncachedZone": {
          "fixed": false,
          "cachable": true,
          "ID": "Asia/Kolkata"
        },
        "fixed": false,
        "ID": "Asia/Kolkata"
      }
    },
    "weekOfWeekyear": 4,
    "secondOfMinute": 14,
    "millisOfDay": 60434153,
    "monthOfYear": 1,
    "dayOfWeek": 1,
    "minuteOfDay": 1007,
    "era": 1,
    "zone": {
      "uncachedZone": {
        "fixed": false,
        "cachable": true,
        "ID": "Asia/Kolkata"
      },
      "fixed": false,
      "ID": "Asia/Kolkata"
    },
    "yearOfCentury": 70,
    "secondOfDay": 60434,
    "millisOfSecond": 153,
    "afterNow": false,
    "equalNow": false,
    "beforeNow": true,
    "dayOfMonth": 19,
    "hourOfDay": 16,
    "centuryOfEra": 19,
    "millis": 1595834153,
    "yearOfEra": 1970,
    "minuteOfHour": 47
  }
}

Conclusion

JWT serves as an effective mode for authentication when the services of an API manager are out of the picture.

Additionally JWTs can be coupled with Spring security framework for added and enhanced security measures.

This is a stateless authentication mechanism as the user state is never saved in server memory. The server’s protected routes will check for a valid JWT in the Authorization header, and if it’s present, the user will be allowed to access protected resources. As JWTs are self-contained, all the necessary information is there, reducing the need to query the database multiple times.

— By Shubhranshu