Written by

Question Alin Soare · Jul 30, 2024

Generate JWT token

Hi there,

My purpose is to encrypt a communication using JWT tokens.  I am developing on IRIS and my purpose is to generate a JWT token that will run on an older version of Cache (so I have to use functions that are compatible with the older version, Cache).

I wrote this code in IRIS:

s username = "user-test123"
set st = ##class(%OAuth2.Utils).TimeInSeconds($ztimestamp,0)
set et = ##class(%OAuth2.Utils).TimeInSeconds($ztimestamp, 60*15)

s payload = {"id": "test-id-123", "username": "test-username"}
s payload.exp = et
s payload.time = st

s alg = "HS256"
s JOSE("sigalg") = alg

s secret = "Dw/G:+@%VR[a$LV,D4L{5+(4I}+zf+ER" // $system.Encryption.GenCryptRand(32)
s secret = $System.Encryption.Base64Encode(secret)

s jwks = { "keys": [{ "alg": (alg), "k": (secret) }] }
s jwks = jwks.%ToJSON()

d ##class(%OAuth2.JWKS).AddOct("HS256",secret,.jwks)

s jwks =  ##class(%DynamicAbstractObject).%FromJSON(jwks)

This code works and the output is so:

 USER>zw
 
JOSE("sigalg")="HS256"
alg="HS256"
et=1722327153
jwks=<OBJECT REFERENCE>[38@%Library.DynamicObject]
payload=<OBJECT REFERENCE>[1@%Library.DynamicObject]
secret="RHcvRzorQCVWUlthJExWLEQ0THs1Kyg0SX0remYrRVI="
st=1722326253
username="user-test123"

USER>zw jwks
jwks={"keys":[{"alg":"HS256","k":"RHcvRzorQCVWUlthJExWLEQ0THs1Kyg0SX0remYrRVI="},{"kty":"oct","k":"UkhjdlJ6b3JRQ1ZXVWx0aEpFeFdMRVEwVEhzMUt5ZzBTWDByZW1ZclJWST0","alg":"HS256"}]}  ; <DYNAMIC OBJECT>

%OAuth2.JWKS.AddOct  generated a key in the 3rd argument, "jwks".   In the same argument I passed the input key, that is jwks.keys.k = "...secret...".

I discovered this by chance, trying many possibilities.   There is no place where I could find a working example that generated a pool of keys of type OAuth2.JWKS.

My first question is, why does it work in this form and, is there a simpler way to generate the JWKS ?

Next, the purpose is to encode a json.  In order for OAuth2.JWT.ObjectToJWT to work, I had to remove the key {"alg":"HS256","k":"RHcvRzorQCVWUlthJExWLEQ0THs1Kyg0SX0remYrRVI="}  that I initially inserted in jwks and keep only the key generated by OAuth2.JWKS.AddOct.  So I did so:

s newjwks = {}
s newjwks.keys=[]
d newjwks.keys.%Push(jwks.%Get("keys").%Get(1))
s newjwks = newjwks.%ToJSON()

d ##class(%OAuth2.JWT).ObjectToJWT(.JOSE, payload, newjwks, newjwks, .JWT)

This indeed worked , the output is so:

USER>w newjwks
{"keys":[{"kty":"oct","k":"UkhjdlJ6b3JRQ1ZXVWx0aEpFeFdMRVEwVEhzMUt5ZzBTWDByZW1ZclJWST0","alg":"HS256"}]} 

USER>w JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InRlc3QtaWQtMTIzIiwidXNlcm5hbWUiOiJ0ZXN0LXVzZXJuYW1lIiwiZXhwIjoxNzIyMzI3MTUzLCJ0aW1lIjoxNzIyMzI2MjUzfQ.dEqq_RHJ_1GEjTLFaklNSfAAAZP9B_PgXbUD1aH2Vkk
USER>

Here, I do not understand why I had to remove the initial key and keep only {"kty":"oct","k":"UkhjdlJ6b3JRQ1ZXVWx0aEpFeFdMRVEwVEhzMUt5ZzBTWDByZW1ZclJWST0","alg":"HS256"} in newjwks.

Product version: Caché 2018.1

Comments

DC AI Bot · May 25 Bot

%OAuth2.JWKS.AddOct("HS256",secret,.jwks) works here because the method appends a properly formed symmetric JWK into the JWKS you pass by reference. The resulting key has the expected JWK shape for a symmetric key, including "kty":"oct" and "alg":"HS256"; the example shown for symmetric JWT creation uses exactly that kind of JWK inside the JWKS before creating the token. [1][2]

A simpler way to build the JWKS is to let the platform create the JWK and then place that JWK into a JWKS, instead of manually inserting a partial key first. The newer example does this in two steps: create the JWK, then add it to the JWKS:

  • ##class(%Net.JSON.JWK).Create("HS256",secret,.privateJWK)
  • ##class(%Net.JSON.JWKS).PutJWK(privateJWK,.privateJWKS) [2]

For Caché 2018 specifically, the available information says %OAuth2.JWKS with AddOct() exists there as well, and that AddOct() is available in Caché 2018.1.9. [3]

The reason your manually inserted key had to be removed is that the key you inserted first was not in the same JWK form as the generated one. Your first entry only had "alg" and "k", while the generated key had "kty":"oct" in addition to "k" and "alg". The documented symmetric JWK example includes "kty":"oct", and the symmetric JWT example uses that generated JWK in the JWKS passed to JWT creation. [1][2]

So the working key is the generated one because it is the proper symmetric JWK representation:

{"kty":"oct","k":"...","alg":"HS256"} [1][2]

The working pattern shown in the available material is therefore:

  1. choose the secret and algorithm,
  2. call AddOct() for %OAuth2.JWKS in the older API, or Create() plus PutJWK() in the newer API,
  3. pass the resulting JWKS to the JWT creation method. [2][3]

One example using the older %OAuth2 API is:

  • set JOSE("sigalg")="HS256"
  • do ##class(%OAuth2.JWKS).AddOct("HS256",secret,.RemotePrivate)
  • do ##class(%OAuth2.JWT).ObjectToJWT(.JOSE,payload,,.RemotePrivate,.jwt) [4]

One example using the newer %Net.JSON API is:

  • Set sc = ##class(%Net.JSON.JWK).Create(algorithm,secret,.privateJWK)
  • Set sc = ##class(%Net.JSON.JWKS).PutJWK(privateJWK,.privateJWKS)
  • Set sc = ##Class(%Net.JSON.JWT).Create(header,,claims,privateJWKS,,.pJWT) [2]

Note: Before 2024, the %OAuth2.JWT class was used for generating JWTs in IRIS. The %Net.JSON.JWT class is now the primary class for JWT creation, and I will use this class in the example code. [2]

Note: For backwards compatibility, the classes still exist under %OAuth2 package, but the codebase now uses %Net.JSON. [5]


Sources:

Tell us more:
Don't like the style
Not factually correct
Didn't fully follow instructions
Refused when it shouldn't have
Being lazy
More...
Submit
0 0