Skip to main content
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Yogesh Mathur
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Yogesh Mathur

Adding RSA Support To JSONWebTokens.cfc - My ColdFusion JWT Library

By
Published in Comments (1)

Last week, I released JSONWebTokens.cfc, which is a small ColdFusion library to facilitate the encoding and decoding of JSON Web Tokens (JWT). When I released it, it only had support for the Hmac (Hashed Message Authentication Code) algorithms. However, I recently took some time to experiment with RSA encryption which uses both a public and a private key. Today, I'm happy to say that I have refactored the JSONWebTokens.cfc to support the RSA portion of the JWT specification.

Project: See the JsonWebTokens.cfc on my GitHub account.

Because the Hmac algorithms use a single key and the RSA algorithms use two keys - public and private - I had to refactor various method signatures to make the second key optional based on the type of algorithm. Ultimately, under the hood, these top-level methods create a Client which is injected with a "Signer" implementation. So, the underlying client doesn't actually care about the algorithm itself - just that it can hand-off the signing and verification of the JWT payload.

Now, the list of supported algorithms includes:

  • HS256 - uses HmacSHA256 as the underlying Java algorithm.
  • HS384 - uses HmacSHA384 as the underlying Java algorithm.
  • HS512 - uses HmacSHA512 as the underlying Java algorithm.
  • RS256 - uses SHA256withRSA as the underlying Java algorithm.
  • RS384 - uses SHA384withRSA as the underlying Java algorithm.
  • RS512 - uses SHA512withRSA as the underlying Java algorithm.

To see the RSA algorithms in action, take a look at this demo which will encode and decode a payload using both the top-level, one-off methods as well as creating a JWT client and using the client methods:

<cfscript>

	// I am the payload to be delivered in a JSON Web Token (JWT).
	payload = {
		question: "How funky is your chicken?",
		followUp: "How loose is your goose?",
		answer: "Our goose is totally loose!"
	};


	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //


	// First, let's try to use the top-level one-off methods.
	jwt = new lib.JsonWebTokens();

	// Encode JWT token using 512-bit RSA signing algorithm.
	token = jwt.encode( payload, "HS512", getPublicKey(), getPrivateKey() );

	// Decode JWT token (and verity signature) using 512-bit RSA signing algorithm.
	decodedPayload = jwt.decode( token, "HS512", getPublicKey(), getPrivateKey() );


	// Verify that all the stuff worked!
	if (
		( payload.question != decodedPayload.question ) ||
		( payload.followUp != decodedPayload.followUp ) ||
		( payload.answer != decodedPayload.answer )
		) {

		throw( "StructEqualityFailed" );

	}

	// Output the decoded values.
	writeOutput( "One-off Methods: <br />" );
	writeOutput( "Question: #decodedPayload.question# <br />" );
	writeOutput( "Follow-Up: #decodedPayload.followUp# <br />" );
	writeOutput( "Answer: #decodedPayload.answer# <br />" );
	writeOutput( "<br />" );


	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //


	// Now, let's try creating a client with encapsulated keys.
	jwtClient = new lib.JsonWebtokens().createClient( "HS512", getPublicKey(), getPrivateKey() );

	// Encode JWT token using encapsulated algorithm and keys.
	token = jwtClient.encode( payload );

	// Decode JWT token (and verify signature) using encapsulated algorithm and keys.
	decodedPayload = jwtClient.decode( token );


	// Verify that all the stuff worked!
	if (
		( payload.question != decodedPayload.question ) ||
		( payload.followUp != decodedPayload.followUp ) ||
		( payload.answer != decodedPayload.answer )
		) {

		throw( "StructEqualityFailed" );

	}

	// Output the decoded values.
	writeOutput( "Client Methods: <br />" );
	writeOutput( "Question: #decodedPayload.question# <br />" );
	writeOutput( "Follow-Up: #decodedPayload.followUp# <br />" );
	writeOutput( "Answer: #decodedPayload.answer# <br />" );


	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //


	// I get the private RSA key in PEM format.
	public string function getPrivateKey() {

		var lines = [
			"-----BEGIN RSA PRIVATE KEY-----",
			"MIICXAIBAAKBgQCq8RpMWK5mEt9qypbw6+zafEgQIpR1uUjHbIbZ6dadW/uxU3DZ",
			"D0KW+KVAXnJb11dYCv48O5O/8h95z/SdAnHchc6hjk7wkAPwG1p3rpMVoSCftkoh",
			"y+qzdELmDir8Sr61gaEXUnZfp2gkfmubiAITHovHFfRk5hNGZTHxol7LJwIDAQAB",
			"AoGAYiVkMAmKuFiFpk8DMviCWT+aMIlqK91iB/4rvtofuuGhNULvO/EjDoNcfgS8",
			"LDcLkyVcq0CZqE9f+xSHIc7RiBegv4uxGJQ6/rivfbOh2KjfLAIkniovN3FOR86B",
			"GJ6MWm3Bo28gGrPoODkRrnZvDHtfRvUXnzyGTxH5yOCJxAECQQDTP4j9yMTuOaag",
			"AG51RQ7cP6aVazZY8e4+MO8+8R0XrU5C1kEzfhh+FGS6laxoaay0LIFnhYB2ozXD",
			"YjiVX+qBAkEAzyepeZQaH9/f6/l3Hnme9Qxfhl+4bnHjR2TEnzb+QLkBnVwYb42P",
			"cO8VnZ1VJadZCX4k2+rEZ1hBc5+yxppRpwJAS4G9NIELqt7eaPhegvohGqaBo4zD",
			"yz0GXCJfkY7bSDhA7fDpMz+R/5bIfky7aELFYU07H8Z/KWii8ehssy+qgQJAVGVI",
			"OmwIKKxAwhakXRoXlKYx1MDylqx3eAKpyGPTOfMloUKAAhKeOdht6gTLR8fiEmf+",
			"BEqlMaVXJRAO+bKtSQJBAIof8obXnfxr4ruNzv7bFSvRSLMXxqOi+5TRbpjpwtRz",
			"yCRXDbakPi05ywzWRDJ/AoON63ZWyFVTFDZQEp2hRwQ=",
			"-----END RSA PRIVATE KEY-----"
		];

		return( arrayToList( lines, chr( 10 ) ) );

	}


	// I get the public RSA key in PEM format.
	public string function getPublicKey() {

		var lines = [
			"-----BEGIN PUBLIC KEY-----",
			"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCq8RpMWK5mEt9qypbw6+zafEgQ",
			"IpR1uUjHbIbZ6dadW/uxU3DZD0KW+KVAXnJb11dYCv48O5O/8h95z/SdAnHchc6h",
			"jk7wkAPwG1p3rpMVoSCftkohy+qzdELmDir8Sr61gaEXUnZfp2gkfmubiAITHovH",
			"FfRk5hNGZTHxol7LJwIDAQAB",
			"-----END PUBLIC KEY-----"

		];

		return( arrayToList( lines, chr( 10 ) ) );

	}

</cfscript>

The RSA keys are assumed to be in plain-text PEM format and passed-in as Strings. Under the hood, they are converted to Java PrivateKey and PublicKey instances. This felt like the easiest way to consume them. And, when we run the above code, we get the following output:

One-off Methods:
Question: How funky is your chicken?
Follow-Up: How loose is your goose?
Answer: Our goose is totally loose!

Client Methods:
Question: How funky is your chicken?
Follow-Up: How loose is your goose?
Answer: Our goose is totally loose!

As you can see, the encoding and decoding worked quite nicely.

I still couldn't tell you exactly what RSA encryption is, or how asymmetric keys work in cryptography. But, at least I can get the JWT algorithms to work when they are supposed to work and fail when they are supposed to fail. So, I'm going to call that a victory!

Want to use code from this post? Check out the license.

Reader Comments

1 Comments

Hi Ben,

I was going through your JSONWebTokens.cfc library on github and found it very useful. I have few questions, I didn't found out any way to set expiry date for the generated tokens and also there is no method by which I can verify the token coming from the client.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel