Prev: Next: Up: Example Lua modules[Contents][Index]


D.1 jwt.lua

This is the implementation of jwt.lua module, used in examples in Lua conditionals. It provides the following methods:

jwt: parse input

Parse input and build a new JWT object on success. Return two values: the resulting object, and error message (on failure).

jwt: new payload secret

Create a new JWT object from payload and encrypt it with the supplied secret.

The JWT object returned by either of these functions has two methods:

Method on jwt: sign secret

Sign the object using secret. Return the signed string suitable for use in Authorization: HTTP header.

Method on jwt: verify secret

The reverse operation: verify if the signature of the object returned by parse matches secret. Return true if it does and false otherwise.

The fields of a JWT object are:

object field of jwt: header

Header part of JWT, decoded into Lua table.

object field of jwt: payload

Payload part, decoded into Lua table.

object field of jwt: signature

Signature part, as a string.

object field of jwt: raw

A table containing original (undecoded) values of the three parts in its fields header, payload, and signature.

Full text of jwt.lua follows:

-- Simple implementation of JWT for Pound documentation.
-- Supports only HMAC signing algorithms.

local jwt = {}

local json = require 'dkjson'
local base64 = require 'base64'

local base64url_encoder = base64.makeencoder( '-', '_' )
local base64url_decoder = base64.makedecoder( '-', '_' )

-- base64url encoding. RFC 7515, Appendix C.
local function base64url_encode(s)
   b = base64.encode(s, base64url_encoder)
   return b:gsub('=*$','')
end

-- base64url encoding. RFC 7515, Appendix C.
local function base64url_decode(s)
   n = #s % 4
   if n == 3 then
      s = s .. '='
   elseif n == 2 then
      s = s .. '=='
   elseif n ~= 0 then
      return nil, 'invalid base64 encoding'
   end
   ok, val = pcall(base64.decode, s, base64url_decoder)
   if ok then
      return val
   else
      return nil, val
   end
end

local openssl = require 'openssl'

-- Constant declarator
local constant = {}
function constant.declare(t)
   return setmetatable(t, {
	 __index = function (table, key)
            local v = rawget(t, key)
	    if v ~= nil then
	       return v
	    else
	       error("No such constant: "..tostring(key))
	    end
	 end;
	 __newindex = function(table, key, value)
	    error("Attempt to modify read-only table")
	 end;
	 __metatable = false;
   });
end

setmetatable(constant, {
		__call = function(_, ...) return constant.declare(...) end
})

local S = constant {
   JWT = "JWT",
}

local function hmac(alg, secret, message)
   return openssl.hmac.hmac(alg, message, secret, true)
end

local sign_algo = constant {
   HS256 = function (secret, message)
	 return hmac('SHA256', secret, message)
   end;
   HS384 = function (secret, message)
	 return hmac('SHA384', secret, message)
   end;
   HS512 = function (secret, message)
	 return hmac('SHA512', secret, message)
   end
}

-- Sign a JWT using supplied secret.
local function jwt_sign(jwt, secret)
   input = jwt.raw.header .. '.' .. jwt.raw.payload
   return base64url_encode(sign_algo[jwt.header.alg](secret, input))
end

-- Verify JWT using supplied secret.  Cache result in the
-- "verified" field.
local function jwt_verify(jwt, secret)
   if jwt.verified == nil then
      jwt.verified = jwt_sign(jwt, secret) == jwt.raw.signature
   end
   return jwt.verified
end

local jwt_metatable = {
   __index = {
      sign = jwt_sign,
      verify = jwt_verify
   }
}

-- Create new JWT object from the encoded JWT input.
-- Return the created object, or nil and textual error description on
-- error.
function jwt.parse(input)
   function split(s, n)
      i = s:find("[.]")
      if i == nil then
	 return s, true
      elseif n == 0 then
	 return s, false
      else
	 return s:sub(1,i-1), split(s:sub(i+1),n-1)
      end
   end

   local r = {}
   r['header'], r['payload'], r['signature'], ok = split(input,2)
   if not ok then
      return nil, 'required JWT parts missing'
   end

   local j = { ['raw'] = r }
   local val, err = base64url_decode(r['header'], base64url_decoder)
   if err ~= nil then
      return nil, 'header fails to decode'
   end

   j['header'], _, err = json.decode(val)
   if err then
      return nil, 'malformed header JSON'
   end
   if j.header.typ ~= S.JWT then
      return nil, 'unsupported type'
   end

   val, err = base64url_decode(r['payload'], base64url_decoder)
   if err ~= nil then
      return nil, 'paiload fails to decode'
   end
   j['payload'], _, err = json.decode(val)
   if err then
      return nil, 'malformed payload JSON'
   end
   setmetatable(j, jwt_metatable)
   return j, nil
end

function jwt.new(payload, secret)
   local j = {}
   j.header = {
      typ = S.JWT,
      alg = "HS256"
   }
   j.payload = payload
   j.raw = {}
   j.raw.header = base64url_encode(json.encode(j.header))
   j.raw.payload = base64url_encode(json.encode(j.payload))
   return j.raw.header .. '.' .. j.raw.payload .. '.' .. jwt_sign(j,secret)
end

return jwt

Prev: Next: Up: Example Lua modules[Contents][Index]