Jump to content

[TOPIC: topicViewTemplate]
[GLOBAL: userSmallPhoto]
Photo

How can we use Json Web Token (JWT)?
Started by akao Sep 04 2018 08:44 AM

4 replies to this topic
[TOPIC CONTROLS]
[/TOPIC CONTROLS]
[modOptionsDropdown]
[/modOptionsDropdown]
[reputationFilter]
[TOPIC: post.html]
#1

akao

[GLOBAL: userInfoPane.html]
akao
  • Contributor

  • 336 posts
  • Corona SDK

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!

 

 



[TOPIC: post.html]
#2

Rob Miracle

[GLOBAL: userInfoPane.html]
Rob Miracle
  • Moderator

  • 24,853 posts
  • Corona Staff

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



[TOPIC: post.html]
#3

akao

[GLOBAL: userInfoPane.html]
akao
  • Contributor

  • 336 posts
  • Corona SDK

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? 



[TOPIC: post.html]
#4

akao

[GLOBAL: userInfoPane.html]
akao
  • Contributor

  • 336 posts
  • Corona SDK

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



[TOPIC: post.html]
#5

akao

[GLOBAL: userInfoPane.html]
akao
  • Contributor

  • 336 posts
  • Corona SDK

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


  • agramonte likes this


[topic_controls]
[/topic_controls]