Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added support for maximum expiration of a JWT token #3331

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions kong/plugins/jwt/handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,15 @@ local function do_authentication(conf)
return false, {status = 401, message = errors}
end

-- Verify the JWT registered claims
if conf.maximum_expiration ~= nil and conf.maximum_expiration > 0 then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second check of this condition seems unnecessary, since you included a safeguard to check_maximum_expiration() already (if max_exp <= 0)

Copy link
Contributor Author

@mvanholsteijn mvanholsteijn May 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to avoid calling the function as I know it is not necessary saving a few CPU cycles.

local ok, errors = jwt:check_maximum_expiration(conf.maximum_expiration)
if not ok then
return false, {status = 403, message = errors}
end
end


-- Retrieve the consumer
local consumer_cache_key = singletons.db.consumers:cache_key(jwt_secret.consumer_id)
local consumer, err = singletons.cache:get(consumer_cache_key, nil,
Expand Down
17 changes: 17 additions & 0 deletions kong/plugins/jwt/jwt_parser.lua
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,23 @@ function _M:verify_registered_claims(claims_to_verify)
return errors == nil, errors
end

--- check the maximum allowed expiration
-- @param maximum_expiration of the claim
-- @return A Boolean indicating true, if the claim the maximum allowed expiration
-- @return error if any
function _M:check_maximum_expiration(maximum_expiration)
if maximum_expiration <= 0 then
return true
end

local exp = self.claims["exp"]
if exp == nil or (exp - ngx_time()) > maximum_expiration then
return false, {exp = "exceeds maximum allowed expiration"}
end

return true
end

_M.encode = encode_token

return _M
18 changes: 18 additions & 0 deletions kong/plugins/jwt/migrations/cassandra.lua
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,22 @@ return {
end,
down = function(_, _, dao) end -- not implemented
},
{
name = "2018-03-15-150000_jwt_maximum_expiration",
up = function(_, _, dao)
for ok, config, update in plugin_config_iterator(dao, "jwt") do
if not ok then
return config
end
if config.maximum_expiration == nil then
config.maximum_expiration = 0
local _, err = update(config)
if err then
return err
end
end
end
end,
down = function(_, _, dao) end -- not implemented
},
}
18 changes: 18 additions & 0 deletions kong/plugins/jwt/migrations/postgres.lua
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,22 @@ return {
end,
down = function(_, _, dao) end -- not implemented
},
{
name = "2018-03-15-150000_jwt_maximum_expiration",
up = function(_, _, dao)
for ok, config, update in plugin_config_iterator(dao, "jwt") do
if not ok then
return config
end
if config.maximum_expiration == nil then
config.maximum_expiration = 0
local _, err = update(config)
if err then
return err
end
end
end
end,
down = function(_, _, dao) end -- not implemented
},
}
26 changes: 26 additions & 0 deletions kong/plugins/jwt/schema.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
local utils = require "kong.tools.utils"
local Errors = require "kong.dao.errors"

local function check_user(anonymous)
if anonymous == "" or utils.is_valid_uuid(anonymous) then
Expand All @@ -8,6 +9,13 @@ local function check_user(anonymous)
return false, "the anonymous user must be empty or a valid uuid"
end

local function check_positive(v)
if v < 0 then
return false, "should be 0 or greater"
end
return true
end

return {
no_consumer = true,
fields = {
Expand All @@ -18,5 +26,23 @@ return {
claims_to_verify = {type = "array", enum = {"exp", "nbf"}},
anonymous = {type = "string", default = "", func = check_user},
run_on_preflight = {type = "boolean", default = true},
maximum_expiration = {type = "number", default = 0, func = check_positive},
},
self_check = function(schema, plugin_t, dao, is_update)
if plugin_t.maximum_expiration ~= nil and plugin_t.maximum_expiration > 0 then
local has_exp = false
if plugin_t.claims_to_verify then
for index, value in ipairs(plugin_t.claims_to_verify) do
if value == "exp" then
has_exp = true
break
end
end
end
if not has_exp then
return false, Errors.schema "to set maximum_expiration, you need to add 'exp' in claims_to_verify"
end
end
return true
end
}
41 changes: 40 additions & 1 deletion spec/03-plugins/17-jwt/03-access_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ for _, strategy in helpers.each_strategy() do

local routes = {}

for i = 1, 10 do
for i = 1, 11 do
routes[i] = bp.routes:insert {
hosts = { "jwt" .. i .. ".com" },
}
Expand Down Expand Up @@ -105,6 +105,12 @@ for _, strategy in helpers.each_strategy() do
config = { key_claim_name = "kid" },
})

plugins:insert({
name = "jwt",
route_id = routes[11].id,
config = { claims_to_verify = {"nbf", "exp"}, maximum_expiration = 300 },
})

plugins:insert({
name = "ctx-checker",
route_id = routes[1].id,
Expand Down Expand Up @@ -254,6 +260,39 @@ for _, strategy in helpers.each_strategy() do
local body = assert.res_status(401, res)
assert.equal([[{"message":"Unauthorized"}]], body)
end)
it("returns 403 if the token exceeds the maximum allowed expiration limit", function()
local payload = {
iss = jwt_secret.key,
exp = os.time() + 3600,
nbf = os.time() - 30
}
local jwt = jwt_encoder.encode(payload, jwt_secret.secret)
local res = assert(proxy_client:send {
method = "GET",
path = "/request/?jwt=" .. jwt,
headers = {
["Host"] = "jwt11.com"
}
})
local body = assert.res_status(403, res)
assert.equal('{"exp":"exceeds maximum allowed expiration"}', body)
end)
it("accepts a JWT token within the maximum allowed expiration limit", function()
local payload = {
iss = jwt_secret.key,
exp = os.time() + 270,
nbf = os.time() - 30
}
local jwt = jwt_encoder.encode(payload, jwt_secret.secret)
local res = assert(proxy_client:send {
method = "GET",
path = "/request/?jwt=" .. jwt,
headers = {
["Host"] = "jwt11.com"
}
})
assert.res_status(200, res)
end)
end)

describe("HS256", function()
Expand Down
21 changes: 21 additions & 0 deletions spec/03-plugins/17-jwt/06-schema_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
local jwt_schema = require "kong.plugins.jwt.schema"

describe("Plugin: jwt (schema)", function()
local ok, res
ok = jwt_schema.self_check(nil, {maximum_expiration = -1}, nil, true)
assert.is_true(ok)

ok, res = jwt_schema.self_check(nil, {maximum_expiration = 300}, nil, true)
assert.is_false(ok)
assert.is_equals(res.message, "to set maximum_expiration, you need to add 'exp' in claims_to_verify")

ok = jwt_schema.self_check(nil, {maximum_expiration = 300, claims_to_verify = {}}, nil, true)
assert.is_false(ok)
assert.is_equals(res.message, "to set maximum_expiration, you need to add 'exp' in claims_to_verify")

ok = jwt_schema.self_check(nil, {maximum_expiration = 300, claims_to_verify = {"exp"}}, nil, true)
assert.is_true(ok)

ok = jwt_schema.self_check(nil, {maximum_expiration = -1, claims_to_verify = {"iss", "exp", "nbf"}}, nil, true)
assert.is_true(ok)
end)