Looks like we’ve solved our own problem. Here is the solution for those who may need it in the future. We were able to make some adjustments to luajwt’s code to make it generate json web token on CoronaSDK (as confirmed using https://jwt.io/). Below is the code. For base64, you should be able to use any number of lua base64 encoder. The one we used is a really old one from Alex Kloss.
-- adopted from https://github.com/x25/luajwt/blob/master/luajwt.lua -- local cjson = require 'cjson' local json = require ("json"); local base64 = require ("base64") local crypto = require ("crypto") local alg\_sign = { ['HS256'] = function(data, key) return crypto.hmac(crypto.sha256, data, key, true) end, ['HS384'] = function(data, key) return crypto.hmac(crypto.sha384, data, key, true) end, ['HS512'] = function(data, key) return crypto.hmac(crypto.sha512, data, key, true) end, } local alg\_verify = { ['HS256'] = function(data, signature, key) return signature == alg\_sign['HS256'](data, key) end, ['HS384'] = function(data, signature, key) return signature == alg\_sign['HS384'](data, key) end, ['HS512'] = function(data, signature, key) return signature == alg\_sign['HS512'](data, key) end, } local function b64\_encode(input) local result = base64.encode(input) result = result:gsub('+','-'):gsub('/','\_'):gsub('=','') return result end local function b64\_decode(input) -- input = input:gsub('\n', ''):gsub(' ', '') local reminder = #input % 4 if reminder \> 0 then local padlen = 4 - reminder input = input .. string.rep('=', padlen) end input = input:gsub('-','+'):gsub('\_','/') return base64.decode(input) end local function tokenize(str, div, len) local result, pos = {}, 0 for st, sp in function() return str:find(div, pos, true) end do result[#result + 1] = str:sub(pos, st-1) pos = sp + 1 len = len - 1 if len \<= 1 then break end end result[#result + 1] = str:sub(pos) return result end local M = {} function M.encode(data, key, alg) if type(data) ~= 'table' then return nil, "Argument #1 must be table" end if type(key) ~= 'string' then return nil, "Argument #2 must be string" end alg = alg or "HS256" if not alg\_sign[alg] then return nil, "Algorithm not supported" end local header = { typ='JWT', alg=alg } local segments = { b64\_encode(json.encode(header)), b64\_encode(json.encode(data)) } local signing\_input = table.concat(segments, ".") local signature = alg\_sign[alg](signing\_input, key) segments[#segments+1] = b64\_encode(signature) return table.concat(segments, ".") end function M.decode(data, key, verify) if key and verify == nil then verify = true end if type(data) ~= 'string' then return nil, "Argument #1 must be string" end if verify and type(key) ~= 'string' then return nil, "Argument #2 must be string" end local token = tokenize(data, '.', 3) if #token ~= 3 then return nil, "Invalid token" end local headerb64, bodyb64, sigb64 = token[1], token[2], token[3] local ok, header, body, sig = pcall(function () return json.decode(b64\_decode(headerb64)), json.decode(b64\_decode(bodyb64)), b64\_decode(sigb64) end) if not ok then return nil, "Invalid json" end if verify then if not header.typ or header.typ ~= "JWT" then return nil, "Invalid typ" end if not header.alg or type(header.alg) ~= "string" then return nil, "Invalid alg" end if body.exp and type(body.exp) ~= "number" then return nil, "exp must be number" end if body.nbf and type(body.nbf) ~= "number" then return nil, "nbf must be number" end if not alg\_verify[header.alg] then return nil, "Algorithm not supported" end if not alg\_verify[header.alg](headerb64 .. "." .. bodyb64, sig, key) then return nil, "Invalid signature" end if body.exp and os.time() \>= body.exp then return nil, "Not acceptable by exp" end if body.nbf and os.time() \< body.nbf then return nil, "Not acceptable by nbf" end end return body end return M