Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Rob Rawlins
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Rob Rawlins@SirRawlins )

JSONWebTokens.cfc - A Small ColdFusion Module For JSON Web Tokens

By Ben Nadel on
Tags: ColdFusion

Recently, I had to use JSON Web Tokens for the first time to integrate with Zendesk's single sign-on system (SSO). JSON Web Tokens are a secure and simple way to pass data (known as claims) between web systems. Essentially, you pass a base64url-encoded JSON payload, along with a secure signature, to another system that will verity your signature (using a shared secret key) and then deserialize your data. To get this working, I created a small ColdFusion module that supports HMac-based signatures.

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

You can use JsonWebTokens.cfc in one of two ways. For one-off use, you can use the .encode() and .decode() methods:

  • JsonWebTokens.encode( payload, secretKey [, algorithm] )
  • JsonWebTokens.decode( token, secretKey [, algorithm] )

Behind the scenes, however, these methods are actually instantiating a JsonWebTokensClient.cfc component for one-off use. If you intend to use the same signing key and hashing algorithm multiple times during the life-cycle of your application, it would be more efficient to just instantiate and cache a client instance:

  • JsonWebTokens.createClient( secretKey [, algorithm] )

This will create a JSONWebTokenClient.cfc with the stored key and algorithm. This component also exposes an .encode() and .decode() method, but only requires the payload and token, respectively, as the key and algorithm are encapsulated:

  • JsonWebTokensClient.encode( payload )
  • JsonWebTokensClient.decode( token )

This is particularly helpful if you want to configure your JsonWebTokensClient implementation during application bootstrap and then inject it into other components without having to worry about passing around your secret key.

More than anything, this was just an excuse for me to think about object design and how different behaviors can be swapped in an out. For example, the client depends on two different encoders: one for the Base64url standard and one for the JSON standard. If you wanted to manually assemble a Client, you could swap in your own implementation. So, if you fell victim to the serializeJson() bug and \u-encodings, you could swap out the JSON-encoder with something "safer."




Reader Comments

@Clayton,

Good question, unfortunately I don't know very much about using certificate-based secret keys in Java. Really, I don't know all that much about cryptography. But, let me try to read through some of that to see if I can figure something out.

Reply to this Comment

@All,

Ok, so I refactored the JsonWebToken.cfc library to allow for RSA-based algorithms:

www.bennadel.com/blog/2942-adding-rsa-support-to-jsonwebtokens-cfc---my-coldfusion-jwt-library.htm

Now, I officially support:

* 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.

Reply to this Comment

@Henry,

First, I looked up how to generate the public and private keys, which lead me to openssl examples. Then, I had to figure out how to read those TEXT files in and convert them to Java keys, which lead me to Java examples. Then, I used the http://jwt.io website to generate a valid signature (they have a testing area).... then, once I had all that, I started to work backwards to get a ColdFusion thing working.

The biggest shift was refactoring the underlying JsonWebTokensClient to not have to know about the hashing. Before, it use to run the hashing internally. But, Hmac and RSA use two different approaches and have a different number of keys.

So, I had to refactor the hashing thinking to make it something that could be substituted. As such, I created two different "signer" components that both support the same interface:

.sign( message ) --> signature
.verify( message, signature ) --> boolean

Now, the hashing choice and the key choice is inside those implementations and the Client only needs to know to call those methods - it doesn't care which approach is being used.

It was fun to think about :D

Reply to this Comment

I am attempting to create a JWT for use with the Google Calendar API.

According to the Google documentation, the JWT is composed of:

{Base64url encoded header}.{Base64url encoded claim set}.{Base64url encoded signature}.

The header is always: {"alg":"RS256","typ":"JWT"}

The claim set is comprised of a JSON representation with values for iss, scope, aud, exp, iat, and sub.

The signature, of course, is created from the header, claim set, and key.

My question is: The payload parameter of the encode function in the JsonWebTokens.cfc is a struct. I have to have a structure that contains the header and the claim set. What is the structure of this struct?

Reply to this Comment

@Richard,

The struct ought to just be the claim set; the header will take care of itself based on the params you passed to the constructor. So something like:
{
"iss": "https://xxx.auth0.com/",
"sub": "auth0|56ea1afe1b3e39f15a8ee52e",
"aud": "3ue9Zc0KslsMfRX3Dvi62JhRouY3xxEr",
"exp": 1459256684,
"iat": 1459170284
}

Reply to this Comment

@Clayton,

Thank you for your assistance.

Right now, I am having other issues, like how to find the public key of my Google public/private key pair.

When a service account is created with domain-wide authority in the Google Developer console, it downloads a json file, supposedly with the public/private key pair.

The json file only contains the following parameters:
type, project_id, private_key_id, private_key, client_email, client_id, auth_uri, token_uri, auth_provider_x509_cert_url, and client_x509_cert_url.

Where is the !@#$% public key in all this?

I couldn't find it in the developer console or my Google Apps for Work admin control panel either. *sigh*

Reply to this Comment

I'm gonna swing and guess the client_x509_cert_url will have what you need. From there you should get the cert with public key; I'd be surprised if it didn't have the public key. Typically, the SDK or middleware will just point to this json file and configure itself to validate your tokens, but Ben's code is more generic than that.

OIDC is going to hopefully fix a lot of these incompatibilities. One OIDC middleware ought to just work with any authority, although I know nobody likes to adhere to a standard. Google may or may not ever switch to full OIDC.

Reply to this Comment

@Clayton,

Thank you Clayton. This file did have the key I was looking for.

Still no luck getting the CFC to operate properly. Will keep on trying and post my results once I get it to work.

Reply to this Comment

My public key is 1024 bytes, and RSASigner.cfc is yelling at me because it is too long. This is the error. A quick Google of the issue has demonstrated that this is not an uncommon issue.

java.security.InvalidKeyException: IOException: DerInputStream.getLength(): lengthTag=127, too big

Unfortunately, I don't have time to dive into Java.

.NET here I come!

Reply to this Comment

I am trying to get this to verify a JWT provided to me by Auth0, and unfortunately, the secret on the site is base64 encoded. And apparently(unless I'm missing something) the token can't be verified with the provided secret, even if i use an online tool to try to decode the secret.
Has anyone else had any experience with verifying an Auth0 token in a CF function?

Thanks in advance.

Reply to this Comment

Larry, I am verifying and decoding Auth0 JWTs with this library. The key here is just the base64 encoded key from Auth0. This is my verification code:

var jwtClient = new JsonWebTokens().createClient("HS256", appSecrets.auth0Secret);
var jwtDecoded = jwtClient.decode(jwt);

Are you by chance using the RS256 signing algorithm? HS256 is the default, you can check in Clients -> <client> -> Advanced Settings -> OAuth or look at the alg field in the JWT header. I am not certain that anyone here has had success with RS256 JWTs, but it should be possible.

Reply to this Comment

Thank you, knowing that someone else is making it work will help.
I will try your supplied code, and with luck, i just mistyped something(that never happens).

I have left all the Defaults on Auth0 at this point in time.
Thanks for the extremely fast response!

Reply to this Comment

So, Still no luck, and I didn't really need to change any of my code.
FYI, I am not attempting to decode the secret at any point in my code.
I don't know if the function will automatically decode it, but I am assuming it is required to be decoded at some point in time. Seeing as when I test the Token on www.jwt.io I have to tell it my secret is base64 Encoded.

Here is the code that I have so far.
Perhaps someone else can see my mistake.

For "testing reasons, this is my current token:

This is a test Application and will be destroyed as soon as I can make this thing work so no issue with security here.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NoYXJlbWUuYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTA4MzA4MTEyMzgxODU5MjM5MzI3IiwiYXVkIjoiYjdvSUJtbm51OWtqQ0M2WGVObTRIMExiY21LUEtrUDYiLCJleHAiOjE0NzMzNjM0OTAsImlhdCI6MTQ3MzM2Mjg5MH0.CRjgoMDdW5VlBGkSkG2aN_seA2BleQiT-v80-xH-RJs

First I get the Token from the Header(I have this with and without the "keyword Bearer"
<cfset tokenNumber = sentheader.headers.Authorization/>
Next i set my secret variable(this is a temp account until i get it working, so I don't mind the "secret" being out there.

<cfset auth0Secret = 'EataFgAkko7rc20TK3PwdDYl5cY7sW2azYL_4mkYZ_fBj425JQJJyRYI1m1ONwHe'/>

then the script to "verify" and this is where it tells me its "an invalid token"

<cfscript>

var jwtClient = new JsonWebTokens().createClient("HS256", "EataFgAkko7rc20TK3PwdDYl5cY7sW2azYL_4mkYZ_fBj425JQJJyRYI1m1ONwHe");
var jwtDecoded = jwtClient.decode(tokenNumber);
</cfscript>

Reply to this Comment

@Larry, I was able to decode your JWT with your secret. If I had to guess it might be that you need to strip off the "Bearer " prefix from the Authorization header.

Reply to this Comment

@Clayton

Thanks,

I'm assuming you didn't need to make any changes to the supplied JsonWebTokens.cfc or other files initially?

I have tried both with and Without Bearer, that was my Initial thought since it showed up in my "output" with the token.

But I have not had that there for quite some time now. For some reason, my API still is not accepting the token as valid. I would like to say that I am typing something wrong, but you made it work, and I'm in not mistaken, we have the same code (with just the variable names different).

Thanks again for the assistance, hopefully I can get this working sometime in the near future.

Reply to this Comment

Well the only think that i can think of is that this is not working due to the fact that I am using Lucee and not the "Official" Cold Fusion.

I get the same error when running the code you have there on my localhost.

Thanks again, I will be back at it again tomorrow.

Reply to this Comment

Well, I got it to work properly.

I did however, have to edit a single line in a single file...

Due to my Secret being base64 Encoded, I changed line 76 from:
//key = charsetDecode( newKey, "utf-8" );

To:
key = binaryDecode( newKey, "base64" );

After about an hour of testing... it works properly every time.
Once again Base64 Secret, for Key provided by Auth0 running on Lucee Server.
Thanks for all the help!

Reply to this Comment

Just a small bug in your README.md code that I ran into doing a proof of concept:

var client = new lib.JsonWebTokens().createClient( "HS256", "secretKey" );

This collides with CF's CLIENT scope. Was scratching my head dumping it out to why it was a struct when it hit me that it was the name of a reserved scope - I don't usually use the CLIENT scope so it didn't jump out at me at first. Should update the example to use a different variable name.

Thanks for all the work! Looking to use this in an upcoming ionic-based app...

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.