JWT Sign Encrypt

Coldfusion [.cfml] based Sign & Encrypt JWT


Introduction:

While signing a JWT provides a means to establish the authenticity of the JWT contents, encryption provides a way to keep the contents of the JWT unreadable to third parties.

An encrypted JWT is known as JWE (JSON Web Encryption).

Encryption:

Encrypt and decrypt values using the Java based asymmetric and symmetric algorithms.

There are 5 segments in a JWE (JSON Web Encryption), rather than 3, as in a JWT (JSON Web Token) or a JWS (JSON Web Signature).

The segments, in order, are:

  • JOSE header
  • JWE Encrypted Key
  • JWE initialization vector
  • JWE Additional Authentication Data (AAD)
  • JWE Ciphertext and JWE Authentication Tag.

The 2nd segment, JWE Encrypted Key, is encrypted, using the RSA-OAEP encryption scheme, which uses an RSA algorithm with the Optimal Asymmetric Encryption Padding (OAEP) method.

The 4th segment, JWE Ciphertext, is encrypted, using the Advanced Encryption Standard (AES) in Galois/Counter Mode (GCM) algorithm with a 256-bit long key. Once decrypted, the JWE Encrypted Key, uses a symmetric key algorithm, specifically designed to be used with Authenticated Encryption with Associated Data (AEAD), to decrypt the JWE Ciphertext or payload.

The associated data, which is part of the AEAD scheme, is stored in the 5th segment (JWE Authentication Tag).

The 3rd and 5th segments, JWE initialization vector and JWE Authentication Tag, are extracted, during the encryption cipher creation, which is used to encrypt segment 4. All segments are Base64 encoded to ensure a successful transit.

The payload is made up of a claim set, which is made up of registered claims and a custom claim:

  • iss = registered claim: issuer
  • sub = registered claim: subject
  • aud = registered claim: audience
  • exp = registered claim: expiration time
  • nbf = registered claim: not before time
  • iat = registered claim: issue time
  • jti = registered claim: JWT id
  • claim = custom claim

None of the claims are mandatory, so only pass in the ones that are required for your project

Installation:

The Java library, used in this installation, requires the Unlimited Strength Java(TM) Cryptography library.

  • Stop Coldfusion Application Server
  • Remove & back up the Standard Strength Java(TM) Cryptography '.jar' files, found at:
[ system_path_to_coldfusion_installation ]\jre\lib\security\local_policy.jar
[ system_path_to_coldfusion_installation ]\jre\lib\security\US_export_policy.jar
  • Add the corresponding Unlimited Strength Java(TM) Cryptography '.jar' files, found in the following directory of this installation:
UnlimitedJCEPolicyJDK8
  • Start Coldfusion Application Server

Set-up

These examples work, out of the box, within the installation directory.

JavaLoader:

  • By default, the installation examples use JavaLoader, but you may wish to take advantage of Coldfusion 10+:
this.javaSettings
  • Uncomment the appropriate entry in:
/application.cfc
  • And then restart the Coldfusion Application Server

  • Use the following argument when initialising the constructor:


new components.jwt.lib.encrypt.Encrypter( ...useJavaLoader = false )

Issues with JavaLoader:

If you experience a well known bug, involving:

com.compoundtheory.classloader.NetworkClassLoader

It may have occured because:

  1. Your component directory maps to '/com', which causes a namespace clash. To resolve this, change your component virtual & physical mapping to anything other than '/com'
  2. There is a memory leak. Please refer to the following article for a resolution: https://www.compoundtheory.com/using-a-java-urlclassloader-in-cfmx-can-cause-a-memory-leak/

JavaLoader Instance:

You may wish to use a JavaLoader singleton instance. One has been created in the:

/application.cfc
  • Use the following argument when initialising the constructor:

new components.jwt.lib.encrypt.Encrypter( ...javaLoaderInstance = request.javaloader )

This keeps the JavaLoader in the application scope which may bring performance benefits, depending on the project requirements.

Code examples

The following working examples can be found in:

/index.cfm

// global variables

jarSystemPath = ExpandPath( "." ) & "\assets\core\lib\chamika-jwt-sign-encrypt\chamika-jwt-sign-encrypt-1.0.8.jar";

javaLoaderClassPath = "components.javaloader.JavaLoader";

Sign & encrypt JWT, using single claim set

Only one custom claim gets parsed


// create claim set

claimset = {
  iss = "https://openid.net",
  sub = "Charles Robertson",
  aud = "https://app-one.com,https://app-two.com",
  exp = DateAdd( "s", ( 1000 * 60 * 10 ), Now() ),
  nbf = Now(),
  iat = Now(),
  jti = CreateUUID(),
  claim = { 
    json = SerializeJson( 
      { 
        forename = "Charles", 
        surname = "Robertson" 
      } 
    ) 
  }
};

// initialise Encrypter.cfc

JwtSignEncrypt = new components.jwt.lib.encrypt.Encrypter( 
  claimSet = claimset, 
  javaLoaderClassPath = javaLoaderClassPath, 
  jarSystemPath = jarSystemPath 
);

// extract secret key

secretKeyEncoded = JwtSignEncrypt.GetSecretKeyEncoded();

// sign & encrypt JWT, using secret key

jwtString = JwtSignEncrypt.Encrypt( secretKeyEncoded = secretKeyEncoded );

// decrypt payload, using JWT and secret key

decryptedJwtString = JwtSignEncrypt.Decrypt( jwtString = jwtString, secretKeyEncoded = secretKeyEncoded );

// format epoch dates

decryptedJwtStringWithParsedDates = {};

for ( key in decryptedJwtString ) {
  value = decryptedJwtString[ key ];
  if ( ListFindNoCase( "exp,nbf,iat", key ) ) {
      date = JwtSignEncrypt.EpochTimeToLocalDate( value );
      value = DateFormat( date, "full" ) & " " & TimeFormat( date, "full" );
  }
  decryptedJwtStringWithParsedDates[ key ] = value;
}

// output payload

WriteDump( var = decryptedJwtStringWithParsedDates );

// check to see whether the JWT has expired

hasExpired = JwtSignEncrypt.HasExpired();

Sign & encrypt JWT, using single claim set and java loader instance

Only one custom claim gets parsed


// create claim set

claimset = {
  iss = "https://openid.net",
  sub = "Charles Robertson",
  aud = "https://app-one.com,https://app-two.com",
  exp = DateAdd( "s", ( 1000 * 60 * 10 ), Now() ),
  nbf = Now(),
  iat = Now(),
  jti = CreateUUID(),
  claim = { 
    json = SerializeJson( 
      { 
        forename = "Charles", 
        surname = "Robertson" 
      } 
    ) 
  }
};

// initialise Encrypter.cfc

JwtSignEncrypt = new components.jwt.lib.encrypt.Encrypter( 
  claimSet = claimset, 
  javaLoaderClassPath = "", 
  jarSystemPath = "",
  useJavaLoader = true, 
  javaLoaderInstance = request.javaloader
);

// extract secret key

secretKeyEncoded = JwtSignEncrypt.GetSecretKeyEncoded();

// sign & encrypt JWT, using secret key

jwtString = JwtSignEncrypt.Encrypt( secretKeyEncoded = secretKeyEncoded );

// decrypt payload, using JWT and secret key

decryptedJwtString = JwtSignEncrypt.Decrypt( jwtString = jwtString, secretKeyEncoded = secretKeyEncoded );

// format epoch dates

decryptedJwtStringWithParsedDates = {};

for ( key in decryptedJwtString ) {
  value = decryptedJwtString[ key ];
  if ( ListFindNoCase( "exp,nbf,iat", key ) ) {
      date = JwtSignEncrypt.EpochTimeToLocalDate( value );
      value = DateFormat( date, "full" ) & " " & TimeFormat( date, "full" );
  }
  decryptedJwtStringWithParsedDates[ key ] = value;
}

// output payload

WriteDump( var = decryptedJwtStringWithParsedDates );

// check to see whether the JWT has expired

hasExpired = JwtSignEncrypt.HasExpired();

Sign & encrypt JWT, using multiple claims

Only one custom claim gets parsed


// create claim set

issuer = "https://openid.net";
subject = "Charles Robertson";
audience = "https://app-one.com,https://app-two.com";
expirationTime = DateAdd( "s", ( 60 * 10 ) , Now() );
notBeforeTime = Now();
issueTime = Now();
jwtID = CreateUUID();
claim = { 
  json = SerializeJson( 
    { 
      forename = "Charles", 
      surname = "Robertson" 
    } 
  ) 
};

// initialise Encrypter.cfc

JwtSignEncrypt = new components.jwt.lib.encrypt.Encrypter( 
  iss = issuer, 
  sub = subject, 
  aud = audience, 
  exp = expirationTime, 
  nbf = notBeforeTime, 
  iat = issueTime, 
  jti = jwtID, 
  claim = claim, 
  javaLoaderClassPath = javaLoaderClassPath, 
  jarSystemPath = jarSystemPath 
);

// extract secret key

secretKeyEncoded = JwtSignEncrypt.GetSecretKeyEncoded();

// sign & encrypt JWT, using secret key

jwtString = JwtSignEncrypt.Encrypt( secretKeyEncoded = secretKeyEncoded );

// decrypt payload, using JWT and secret key

decryptedJwtString = JwtSignEncrypt.Decrypt( jwtString = jwtString, secretKeyEncoded = secretKeyEncoded );

// format epoch dates

decryptedJwtStringWithParsedDates = {};

for ( key in decryptedJwtString ) {
  value = decryptedJwtString[ key ];
  if ( ListFindNoCase( "exp,nbf,iat", key ) ) {
      date = JwtSignEncrypt.EpochTimeToLocalDate( value );
      value = DateFormat( date, "full" ) & " " & TimeFormat( date, "full" );
  }
  decryptedJwtStringWithParsedDates[ key ] = value;
}

// output payload

WriteDump( var = decryptedJwtStringWithParsedDates );

// check to see whether the JWT has expired

hasExpired = JwtSignEncrypt.HasExpired();

Decrypt stored JWT string and secret key encoded

Only one custom claim gets parsed


// initialise Encrypter.cfc

JwtSignEncrypt = new components.jwt.lib.encrypt.Encrypter( 
  javaLoaderClassPath = javaLoaderClassPath, 
  jarSystemPath = jarSystemPath 
);

// retrieve secret key
      
secretKeyEncoded = FileReadBinary( ExpandPath( "." ) & "\secretKeyEncoded.txt" );

// retrieve JWT string
        
jwtString = REReplaceNocase( Trim( FileRead( ExpandPath( "." ) & "\jwtString.txt" ) ), "[\s]+", "", "ALL" );

// decrypt payload, using JWT and secret key

decryptedJwtString = JwtSignEncrypt.Decrypt( jwtString = jwtString, secretKeyEncoded = secretKeyEncoded );

// format epoch dates

decryptedJwtStringWithParsedDates = {};

for ( key in decryptedJwtString ) {
  value = decryptedJwtString[ key ];
  if ( ListFindNoCase( "exp,nbf,iat", key ) ) {
      date = JwtSignEncrypt.EpochTimeToLocalDate( value );
      value = DateFormat( date, "full" ) & " " & TimeFormat( date, "full" );
  }
  decryptedJwtStringWithParsedDates[ key ] = value;
}

// output payload

WriteDump( var = decryptedJwtStringWithParsedDates );

// check to see whether the JWT has expired

hasExpired = JwtSignEncrypt.HasExpired();

Sign & encrypt JWT, using single claim set and custom secret key

Only one custom claim gets parsed


// create claim set

claimset = {
  iss = "https://openid.net",
  sub = "Charles Robertson",
  aud = "https://app-one.com,https://app-two.com",
  exp = DateAdd( "s", ( 1000 * 60 * 10 ), Now() ),
  nbf = Now(),
  iat = Now(),
  jti = CreateUUID(),
  claim = { 
    json = SerializeJson( 
      { 
        forename = "Charles", 
        surname = "Robertson" 
      } 
    ) 
  }
};

secretKey = Hash( "foo" );

// initialise Encrypter.cfc

JJwtSignEncrypt = new components.jwt.lib.encrypt.Encrypter( 
  claimSet = claimset, 
  secretKey = secretKey, 
  javaLoaderClassPath = javaLoaderClassPath, 
  jarSystemPath = jarSystemPath 
);

// extract secret key

secretKeyEncoded = JwtSignEncrypt.GetSecretKeyEncoded();

// sign & encrypt JWT, using secret key

jwtString = JwtSignEncrypt.Encrypt( secretKeyEncoded = secretKeyEncoded );

// decrypt payload, using JWT and secret key

decryptedJwtString = JwtSignEncrypt.Decrypt( jwtString = jwtString, secretKeyEncoded = secretKeyEncoded );

// format epoch dates

decryptedJwtStringWithParsedDates = {};

for ( key in decryptedJwtString ) {
  value = decryptedJwtString[ key ];
  if ( ListFindNoCase( "exp,nbf,iat", key ) ) {
      date = JwtSignEncrypt.EpochTimeToLocalDate( value );
      value = DateFormat( date, "full" ) & " " & TimeFormat( date, "full" );
  }
  decryptedJwtStringWithParsedDates[ key ] = value;
}

// output payload

WriteDump( var = decryptedJwtStringWithParsedDates );

// check to see whether the JWT has expired

hasExpired = JwtSignEncrypt.HasExpired();

1.1.0

Added new argument to Encrypter.cfc constructor, called:

javaLoaderInstance=request.javaloader

This takes a singleton instance, provided in the application.cfc, which is stored in the application scope and passed to the request scope.

This optional argument will allow the javaloader and its loaded java class to remain in memory, improving performance.

Here are all the versions for this package. Please note that you can leverage CommandBox package versioning to install any package you like. Please refer to our managing package version guide for more information.

Version Created Last Update Published By Stable Download
1.1.1 Jan 27 2019 10:26 AM Jan 27 2019 10:26 AM Charles Robertson (Charlesr1971)
1.1.0 Jan 27 2019 09:42 AM Jan 27 2019 09:42 AM Charles Robertson (Charlesr1971)
1.0.3 Jan 27 2019 09:39 AM Jan 27 2019 09:39 AM Charles Robertson (Charlesr1971)
1.0.2 Jan 22 2019 05:44 PM Jan 22 2019 05:44 PM Charles Robertson (Charlesr1971)
1.0.1 Jan 22 2019 03:42 PM Jan 22 2019 03:42 PM Charles Robertson (Charlesr1971)
1.0.0 Jan 18 2019 01:12 PM Jan 18 2019 01:12 PM Charles Robertson (Charlesr1971)

 

  •   Charles Robertson
  • Published
  • 1.1.1 is the latest of 6 release(s)
    Published
  • Published on Jan 27 2019 10:26 AM
No collaborators yet.
 
  • Jan 18 2019 01:12 PM
  • Jan 27 2019 10:26 AM
  • 180
  • 116
  • 0