Monday, 12 May 2014

Handling a JSON Web Token (JWT)

So I've been working on using some Google authentication for a Uniface web application, and it's clever stuff.  However, being the security conscious people that they are, they use a JSON Web Token (JWT) - pronounced "jot", apparently.

To quote the abstract...
The claims in a JWT are encoded as a JSON object that is digitally signed using JSON Web Signature (JWS) and/or encrypted using JSON Web Encryption (JWE).

The Google API documentation is pretty good.  It gives you an endpoint that you can use to verify the token for debugging purposes, but suggests that in production you should be doing the verification locally...
Fortunately, there are well-debugged libraries available in a wide variety of languages to accomplish this.

I did look, of course I did, but did I expect to find Uniface on that list?  No I did not.  They have examples for .NET, Java, PHP, Python and Ruby.  So this is me, trying it the hard way, in Uniface.

I should point out, there are a few caveats to this...

  1. I've not done anything with encrypted tokens (JWEs).
  2. For signed tokens (JWSs) I've not validated the signature - I tried, but I can't get Uniface to do the encryption properly - I have a FrontLine call open about this.
  3. I use an included procedure "json_to_list" in a few places - this is something I'd previously written which uses string manipulation to convert a JSON string into a Uniface list.
  4. This code is provided as is, with no guarantee that it will work, it is merely for demonstration purposes. 

entry jwt_to_list
  string pToken : in ;JSON Web Token (JWT)
  string pList : out ;Uniface list of data
  string vToken,vHeader,vHeaderJson,vHeaderList,vAlgorithm,vTokenMode,vTokenType
  string vEncryption,vKeyId,vKeyUrl,vPartTwo,vPartTwoJson,vPartTwoList,vPartThree
  ;check parameters
  if ( pToken = "" )
    return -101 ;no token

  ;split token into 3 parts
  vToken = $replace(pToken,1,".","·;",-1)
  if ( $itemcount(vToken) != 3 )
    return -102 ;token doesn't have 3 parts
  getitem vHeader,vToken,1
  getitem vPartTwo,vToken,2
  getitem vPartThree,vToken,3
  if ( vHeader = "" | vPartTwo = "" )
    return -103 ;token parts are missing (check third part later, depends on mode)

  ;decode header
  vHeaderJson = $replace($replace(vHeader,1,"_","/",-1),1,"-","+",-1)
  vHeaderJson = $encode("USTRING",$decode("BASE64",vHeaderJson))
  if ( $status < 0 | $procerror < 0 | vHeaderJson = "" )
    return -104 ;header could not be decoded
  call json_to_list(vHeaderJson,vHeaderList)
  if ( vHeaderList = "" )
    return -105 ;header JSON is invalid

  ;extract header values
  getitem/id vTokenType,vHeaderList,"typ"
  delitem/id vHeaderList,"typ"
  getitem/id vAlgorithm,vHeaderList,"alg"
  delitem/id vHeaderList,"alg"
  getitem/id vEncryption,vHeaderList,"enc"
  delitem/id vHeaderList,"enc"
  getitem/id vKeyId,vHeaderList,"kid"
  delitem/id vHeaderList,"kid"
  getitem/id vKeyUrl,vHeaderList,"jku" 
  delitem/id vHeaderList,"jku"
  if ( vHeaderList != "" )
    return -106 ;unknown header values

  ;check signature algorithm
  selectcase ( vAlgorithm )
    case "none"
      vAlgorithm = "" ;plaintext token
      vTokenMode = "JWT"
    case "HS256"      
      vAlgorithm = "HMAC_SHA256" ;HMAC using SHA-256 hash
      vTokenMode = "JWS"
    case "HS384"
      vAlgorithm = "HMAC_SHA384" ;HMAC using SHA-384 hash
      vTokenMode = "JWS"
    case "HS512"
      vAlgorithm = "HMAC_SHA512" ;HMAC using SHA-512 hash
      vTokenMode = "JWS"
    case "RS256"
      vAlgorithm = "RSASSA_PKCS1V15_SHA256" ;RSA SSA (PKCS) using SHA-256 hash
      vTokenMode = "JWS"
    case "RS384"
      vAlgorithm = "RSASSA_PKCS1V15_SHA384" ;RSA SSA (PKCS) using SHA-384 hash
      vTokenMode = "JWS"
    case "RS512"
      vAlgorithm = "RSASSA_PKCS1V15_SHA512" ;RSA SSA (PKCS) using SHA-512 hash
      vTokenMode = "JWS"
    case "PS256"
      vAlgorithm = "RSASSA_PSS_SHA256" ;RSA SSA (PSS) using SHA-256 hash
      vTokenMode = "JWS"
    case "PS384"
      vAlgorithm = "RSASSA_PSS_SHA384" ;RSA SSA (PSS) using SHA-384 hash
      vTokenMode = "JWS"
    case "PS512"
      vAlgorithm = "RSASSA_PSS_SHA512" ;RSA SSA (PSS) using SHA-512 hash
      vTokenMode = "JWS"
    case "RAS1_5"
      vAlgorithm = "RSAES_PKCS1V15" ;RSA ES (PKCS)
      vTokenMode = "JWE"
    case "RSA-OAEP-256"
      vAlgorithm = "RSAES_OAEP_SHA256" ;RSA ES (OAEP) using SHA-256 hash
      vTokenMode = "JWE"
    case "ES256","ES384","ES512","RSA-OAEP","A128KW","A192KW","A256KW","dir"
      return -107 ;valid, but not supported by Uniface   
    case "","alg"
      return -108 ;no algorithm (mandatory value)
      return -109 ;unknown algorithm specified

  ;check mode/third part
  selectcase ( vTokenMode )
    case "JWT"
      if ( vPartThree != "" )
        return -110 ;third part specified in plaintext token
    case "JWS"
      if ( vPartThree = "" )
        return -111 ;third part missing in signed token
    case "JWE"
      if ( vPartThree = "" )
        return -111 ;third part missing in encrypted token
      return -112 ;todo - handle this type
      return -112 ;unknown token mode

  ;decode second part
  vPartTwoJson = $replace($replace(vPartTwo,1,"_","/",-1),1,"-","+",-1)
  vPartTwoJson = $encode("USTRING",$decode("BASE64",vPartTwoJson))
  if ( $status < 0 | $procerror < 0 | vPartTwoJson = "" )
    return -113 ;second part could not be decoded
  call json_to_list(vPartTwoJson,vPartTwoList)
  if ( vPartTwoList = "" )
    return -114 ;second part JSON is invalid

  ;signature mode
  if ( vTokenMode = "JWS" )
    ;todo – validate signature
  ;return data
  if ( vTokenType = "JWS" | vTokenType = "JWE" )
    call jwt_to_list(vPartTwoJson,pList) ;handle nested tokens
    pList = vPartTwoList ;no nesting, just return data

  return 0

Summary:  It is possible to handle JSON Web Tokens (JWTs), but so far I've only looked at plaintext and signed tokens, and I've not managed to validate the signature for signed tokens yet.


  1. Hey Rik,

    Great post, thanks for writing it up.

    I'm sure you're aware of this, but just in case, what version of Uniface are you on? 9.6.04 has a jsontostruct command which might simplify things a bit?

    1. Thanks. Yes, the reason I did post the code for the "json_to_list" entry is because I'm sure it could now be re-written using "jsontostruct". I've not had a chance to look at this yet though, but thank you for mentioning it.

  2. Hey Rik!

    Why are you changing - by / and - by +?

    vHeaderJson = $replace($replace(vHeader,1,"_","/",-1),1,"-","+",-1)