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.
‘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.
- 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);
}
}
}
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.
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.
Fig: generateTokenFlow with Mule 4
Details
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.
Fig: verifyTokenFlow with Mule 4
Details
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