Skip to main content
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Maximilian Kwapil
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Maximilian Kwapil

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

By
Published in Comments (31)

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

15,902 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.

15,902 Comments

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

15,902 Comments

@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

5 Comments

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?

5 Comments

@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
}

5 Comments

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

5 Comments

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.

5 Comments

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

5 Comments

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!

6 Comments

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.

5 Comments

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.

6 Comments

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!

6 Comments

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>

5 Comments

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

6 Comments

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

6 Comments

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.

6 Comments

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!

1 Comments

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

1 Comments

Hi Ben,

First of all, Thanks for making this CFC. You are save my day.

I am having a problem to initiate this CFC using "SHA256withRSA". I have a private and public key but it's giving an error saying:
" The input and output encodings are not same "

Do you know what's causing this problem. Thanks.

Here is my Private Key:
-----BEGIN PRIVATE KEY-----MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwv8zJ0JQ8NuooJtunanflvWQDw8M4TIE53qz0txUrFM829OEjsviSqIdHhw28iv1qGOJysG7T5KeDA1JASqgWb0IJLX2dcy9SmZfoCwgwFpx1PZ7oT08bskMvbw+HXsrv1njmA5jHEDUHgV8rOzIwtFfxciM1+iWALs1CKNGrVabqomOazWE6+VMb2rNR/xmQnbwTDO2bUTK3A601yLdxKcH5ywBDlYUF1qVlExjNCDaMsNJnYM7EessdnoEVvOwo35T2UQkhqExrmGgP4Ml1GQzLZFzJQ2cQt5FHdSr9b5aYEyayCy4l9cvMhwezM7r3Vs+28F5FLU874n7J/WpzAgMBAAECggEATchKbjINWiZsi7j2B0oSc5cSMZnwCpGYeQgmoxcDCM+ClVPU1GoJfDkQN4kWoXelKBDFbuV4EGXa1gwbIVH5gM37iKBtXGH5XGddc2+PbeI1fl7c1K06Shz79h7w/YjY1A1VbRv+GvRl8eScJiFJ6CclYIrzNejcofl2uPvPsY8nm+BUuqVJs/awiIY4VY218AFjnK8tv3qJv9Y2CgPhG3yYk+3oAQ/MXs3ea+sWtvThGRIuASP/KsTFWr6Ivzpynl2e4PcC1HZcZS1GcJHEwEIjhPn6GOqGNRv5m0oOOsuuusufYn2AfYkwy87+5zVyN2IicQ/5wEqK8Hnkbgat8QKBgQDeNoi4d0HKpAieFONwoEX7raFFhHwVeGMlKf1n+B3eJsCKD90QDr0nU2hoe5WqLBS8jfzN94ArX9HoZ+28tBTbUT5OfQMLc/mx3Hpen2oOsUXvoEIboEHxBUD4RmqEhvjq+zK1xKUYCkCzZ2OH5OCD4uHZU4K6bjJKdORXHkJBWQKBgQDLn5+PuGZd9PaB/uiNx8xGtgiwaWKRi/E6hgV/1L3ugGz2r7k7UUuaQRr6CAmZrZp0xGtUO0/z3g1Iph9epyETvNSYefHZLxia75rpUUndum8/yzHijJU20EifB1jD+kSPK0Z7HcQ4HOHEOv7+Z/hKu8MjHTRc+nBFm/rfID1kqwKBgAFBKZBxC+wK9Ql/cLFIFl0PY+OylcjBPz4MeKPHoNOKQHgHEuZuTt8T93jvia7GRO+zlYPMK+sSxWqce5kfIycB1Go5BnVgGD7K0d4K5Fxk39I0g2sIgpbLwswkCaHp6SnadTTVgWEbXbYllUhFIIdeFoxVjl1jFeGZu1hKkXExAoGBAJuAlnlKaJq+VXbnNa/48k7Z+37FrGnchmecLBz4WegHGa+2eO4M4oC10aU6eCJtirQ7wH9+IEvruxdNDk3ZspABOBYNop2qss63xwUBeDilhQoh4kHR/K30mFEiai6YNL1lXhjtnYkAvYV/THb67YodJCPvzKpo806llQvK8jerAoGBANu9SQ9TFA90Joay1cFUHpjJhvOz8glkankCLYFlgXALO1lId/EWFJ6VaoJy/701s602ErqrKKiAgzsrvCoX6ssMKimlNymlxY7zpR2UQHAWGHM+kzOl778T1ZPQNQ81FKlk5lZ0UiNLxzwSEtXzyKdsXInPkBhe7KOgZGUV9AlF-----END PRIVATE KEY-----

and here my public key:
-----BEGIN PUBLIC KEY----- 110534310540508509989 -----END PUBLIC KEY-----

2 Comments

@Richard,
I was glad to see someone else trying to authenticate with Coldfusion to Google Calendar API through a service account. I have been poring over everything I can find but still have not been successful at figuring out how to create the signature. They recommend using the JAVA or .NET libraries, and I would love to do that, but don't know how to incorporate. You said you had a measure of success using .NET. It's been a year now, how is it going? Would you mind sharing how you did it?

1 Comments

I'm working with an authentication system that has given me a x509 certificate for the public key and when I try to decode the token using this library, I get this error: "Invalid RSA public key encoding."
I am able to successfully validate the signature on the jwt.io decoder so I'm pretty sure it's not a problem with the tokens or the certificate, but rather something in the Java being used by Coldfusion. I've tried tweaking it as much as I know how and I've gotten nowhere. Do you have any suggestions?

I'm pretty sure my code should work:

<cfset jwt = new lib.JsonWebTokens()>
<cfset payload = jwt.decode( token, "RS256", "#certString#" )>
Or am I missing something?
Thanks!

1 Comments

@Joseph,

Did you ever get past this error? I'm trying to use this for Google OAuth2 (RS256) and was able to pull the public key from the client_x509_cert_url URL in the JSON (private key explicitly provided) but not able to create a JWT client or directly encode the assertion. All I get is "The input and output encodings are not same."

Thanks!

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