JSON Web Token Helper
The JSON Web Token Helper contains handlers which allows you to generate, decode and verify JSON Web Tokens. It follows the industry standard RFC 7519 which is a means for representing claims as a JSON object to be transferred between two parties. The JWT helper uses the HMAC SHA256, SHA384 or SHA512 hashing algorithm to sign tokens and a RSA public/private key pair to encrypt the hashed signing input in case an asymmetric signing algorithm is preferred. The RSA key length is 4096 bits.
If you are not familiar with JSON Web Tokens you can read about the concept at https://jwt.io.
Note: If the server engine used is not LC Create, this helper requires the LiveCode Builder (library) extension com.livecode.library.json which needs to be stored in application/extensions. So, the path to the extension is application/extensions/com.livecode.library.json/module.lcm. Please read about how to include/load the LiveCode JSON extension in chapter "Extensions".
Note: HMAC SHA384 and SHA512 hashing algorithms are not supported on LiveCode server prior to version 9.
Loading this Helper
This helper is loaded using the following code:
rigLoadHelper "jwt"
The Key
The key (secret) used to sign the token should be saved to a file named "jwt.lc" in application/config. Preferably this key is a binary string which implicitly needs to be base64 encoded. If you don't specify a key it will be generated for you - provided that file permissions allow - using the LiveCode randomBytes(64) function and saved to application/config/jwt.lc on calling the handler to encode a token. Keep in mind that if your server is not totally under your control it's impossible to ensure key security so you may want to think carefully before using it for anything that requires high security.
The RSA Public/Private Key Pair
The optional RSA public/private key pair used to encrypt the hashed signing input should be saved to a file "jwtPrivate4096" and "jwtPublic4096" respectively in application/config. If the keys are missing they will be generated for you on first request provided that file permissions allow and that the OpenSSL suite of command line tools is installed.
Handler Reference
The following handlers are available:
rigJWTencode(pHeader, pPayload, pNumBits, pAlg)
This function generates and returns a JSON Web Token.
Parameters
- pHeader: (optional) is used to build the header object and is generated automatically if not defined, usually you can leave this parameter empty or else it needs to be a comma delimited key value list or an array, the header generated by revIgniter consists of the type of the token, which is "JWT" and the appropriate signing algorithm ("HS256" on LiveCode below version 9, otherwise the algorithm is determined by the value of the third and fourth optional parameters)
- pPayload: can be a comma delimited key value list or an array
- pNumBits: (optional) is used to determine the size of the hash (digest). Valid values are "256", "384", "512" or empty, if the parameter is empty the default value on LiveCode server below version 9 is "256", otherwise "512" is used
- pAlg: (optional) is used to determine the signing algorithm, "HS" or "RS"
Example using the default header and the default signing algorithm (HS256 on LIveCode server below vesion 9 and HS512 on LiveCode server version 9 or higher):
put the seconds into tIAT
put tIAT + 10 into tNBF
put tIAT + 60 into tEXP
put uuid() into tJTI
put "sub, jwt test, name," && tUsername & ", admin, true, iat," && tIAT & ", nbf," \
&& tNBF & ", exp," && tEXP & ", jti," && tJTI into tJWTpayload
put rigJWTencode( , tJWTpayload) into tJWT
put tJWT into tTokenA["jwt"]
# rigSetHeader DOES NOT WORK WITH AJAX, USE: put header ...
# GENERATES AN OBJECT ON CLIENT SIDE. SO, DON'T USE JSON.parse() TO PROCESS THE DATA
put header "Content-type: application/json; charset=UTF-8"
put JsonExport(tTokenA)
Following a variant using an array as second parameter:
put the seconds into tIAT
put tIAT + 10 into tNBF
put tIAT + 60 into tEXP
# NOTE: SECONDS NEED TO BE A STRING, OTHERWISE THE DATA OF JSONEXPORT IS NOT VALID.
put tIAT & "" into tJWTpayloadA["iat"]
put tNBF & "" into tJWTpayloadA["nbf"]
put tEXP & "" into tJWTpayloadA["exp"]
put "jwt test" into tJWTpayloadA["sub"]
put tUsername into tJWTpayloadA["name"]
put "true" into tJWTpayloadA["admin"]
put uuid() into tJWTpayloadA["jti"]
put rigJWTencode( , tJWTpayloadA) into tJWT
put tJWT into tTokenA["jwt"]
# rigSetHeader DOES NOT WORK WITH AJAX, USE: put header ...
# GENERATES AN OBJECT ON CLIENT SIDE. SO, DON'T USE JSON.parse() TO PROCESS THE DATA
put header "Content-type: application/json; charset=UTF-8"
put JsonExport(tTokenA)
These two examples above assume that the client side provides code which handles the response in form of a JSON object.
Example for the assignment of a signing algorithm, in this case RSA384, using the default header:
put rigJWTencode( , tJWTpayload, 384, "RS") into tJWT
rigJWTdecode(pToken)
Decode and validate a JSON Web Token.
Parameters
- pToken: (optional) is representing a token, if not provided the function assumes that the client side has sent the JWT in an "Authorization" header using the "Bearer" schema (Authorization: Bearer <token>)
Example:
put "The secret data." into tSecretData
put rigJWTdecode() into tJWTA
if tJWTA["valid"] is TRUE then
put the seconds into tCurrentTime
put TRUE into tTimeValid
if tCurrentTime < tJWTA["payload"]["nbf"] then
put "Token not yet valid." into tA["responsedata"]
put FALSE into tTimeValid
else if tCurrentTime > tJWTA["payload"]["exp"] then
put "Token expired." into tA["responsedata"]
put FALSE into tTimeValid
end if
if tTimeValid is TRUE then
if tJWTA["valid"] then
put tSecretData into tA["responsedata"]
else
put tJWTA["response"] into tA["responsedata"]
end if
end if
else-- if tJWTA["valid"] is TRUE
# RESPONSE IS ERROR "HTTP/1.0 400 Bad Request" OR "HTTP/1.0 401 Unauthorized"
put tJWTA["response"] into tA["responsedata"]
rigSetStatusHeader word 2 of tJWTA["response"]
end if -- if tJWTA["valid"] is TRUE
# rigSetHeader DOES NOT WORK WITH AJAX, USE: put header ...
# GENERATES AN OBJECT ON CLIENT SIDE. SO, DON'T USE JSON.parse() TO PROCESS THE DATA
put header "Content-type: application/json; charset=UTF-8"
put JsonExport(tA)
The example above assumes that the client side provides code which handles the response in form of a JSON object.
Data Returned
This function returns an array including the following keys:
- "header"
(the value is the token header as array) - "payload"
(the value is the token payload as array) - "valid"
(the value is a boolean, this refers to the integrity of the token) - "response"
(the value is empty in case the token is valid, otherwise it is a string "HTTP/1.0 401 Unauthorized", in case the token can not be decoded the string is "HTTP/1.0 400 Bad Request" - use these strings as response header if something went wrong and the integrity of the token could not be determined)