How can we use Json Web Token (JWT)?

We are integrating to a backend service that uses json web token for authorization. Has anyone used json web token via Corona in the past? Does it work? 

We’ve found some lua libraries online but they seem to be mostly for server-side, and it’s not clear if these are lua-only libraries or if they needed native libraries to work. I am interested to see if anyone has ever attempted it with Corona and how they may have gotten it to work. 

Many thanks!

It looks to me after researching JWT a bit that you will somehow get the token into your Corona app, perhaps as the result of network.request() call or part of data structure provided by some other service. I didn’t see anything about how you get the JWT, just how it’s formatted.

Now once you have it, you would probably want to split the JWT into its two or three components, then perhaps find a public Lua function that can decode base64 that’s also been URL encoded. At that point you can json.decode() the three components into Lua tables and access their values.

Now how you go about getting the public key used to sign and or encrypt the token goes beyond my knowledge. Hopefully, this will get you started.

Rob

Thanks for the quick response Rob…

I think the part we are stuck at is precisely the encryption part. Looking at this:

https://jwt.io/

and talking with our server guy, looks like HS256 is the most standard encryption approach. That site refers to this as the Lua library to use:

https://github.com/SkyLothar/lua-resty-jwt.git

Digging into this library, it appears to rely on a number of lua libraries, many of which depends on the ffi library and LuaJit. Now - are these dependencies happen to be available via Corona? 

It feels like I am pretty dead in the water, but want to see if anyone’s figure it out? 

digging a bit more, looks like https://github.com/x25/luajwt may be helpful… Looking into it a bit more. 

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

It looks to me after researching JWT a bit that you will somehow get the token into your Corona app, perhaps as the result of network.request() call or part of data structure provided by some other service. I didn’t see anything about how you get the JWT, just how it’s formatted.

Now once you have it, you would probably want to split the JWT into its two or three components, then perhaps find a public Lua function that can decode base64 that’s also been URL encoded. At that point you can json.decode() the three components into Lua tables and access their values.

Now how you go about getting the public key used to sign and or encrypt the token goes beyond my knowledge. Hopefully, this will get you started.

Rob

Thanks for the quick response Rob…

I think the part we are stuck at is precisely the encryption part. Looking at this:

https://jwt.io/

and talking with our server guy, looks like HS256 is the most standard encryption approach. That site refers to this as the Lua library to use:

https://github.com/SkyLothar/lua-resty-jwt.git

Digging into this library, it appears to rely on a number of lua libraries, many of which depends on the ffi library and LuaJit. Now - are these dependencies happen to be available via Corona? 

It feels like I am pretty dead in the water, but want to see if anyone’s figure it out? 

digging a bit more, looks like https://github.com/x25/luajwt may be helpful… Looking into it a bit more. 

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

Hi @akao,

Could use your help with Google Play receipt validation. I’m trying to use the same method you described above and generate an authentication token but I’m still getting authError from Google Server. I’m trying to make a request using a service account credentials. Unfortunately, I could not find any solid reference on this subject. Could you share how you use jwt to produce a correctly formatted request for Google server?
I’ve been trying to follow the example Google provided at the end of the page here: https://developers.google.com/identity/protocols/oauth2/service-account#python_2