Skip to content

Commit

Permalink
feat(jwt): add claims_to_reject list
Browse files Browse the repository at this point in the history
If the JWT contains any of the listed claim names, the request
is rejected with a 401.
  • Loading branch information
Tieske committed May 6, 2024
1 parent 8470056 commit 097fce4
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 3 deletions.
3 changes: 3 additions & 0 deletions changelog/unreleased/kong/feat_jwt_reject-claims.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
message: |
Adds a `jwt_reject_claims` configuration option to the JWT plugin to allow for rejecting tokens that contain specific claims.
type: feature
8 changes: 8 additions & 0 deletions kong/plugins/jwt/handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,14 @@ local function do_authentication(conf)
local claims = jwt.claims
local header = jwt.header

if conf.claims_to_reject then
for _, claim in ipairs(conf.claims_to_reject) do
if claims[claim] then
return false, { status = 401, message = "Token rejected due to claim " .. claim }
end
end
end

local jwt_secret_key = claims[conf.key_claim_name] or header[conf.key_claim_name]
if not jwt_secret_key then
return false, { status = 401, message = "No mandatory '" .. conf.key_claim_name .. "' in claims" }
Expand Down
6 changes: 6 additions & 0 deletions kong/plugins/jwt/schema.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ return {
type = "string",
one_of = { "exp", "nbf" },
}, }, },
{ claims_to_reject = {
description = "A list of claims that if present will lead to an auth failure.",
type = "set",
elements = {
type = "string",
}, }, },
{ anonymous = { description = "An optional string (consumer UUID or username) value to use as an “anonymous” consumer if authentication fails.", type = "string" }, },
{ run_on_preflight = { description = "A boolean value that indicates whether the plugin should run (and try to authenticate) on OPTIONS preflight requests. If set to false, then OPTIONS requests will always be allowed.", type = "boolean", required = true, default = true }, },
{ maximum_expiration = {
Expand Down
66 changes: 63 additions & 3 deletions spec/03-plugins/16-jwt/03-access_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ for _, strategy in helpers.each_strategy() do

local routes = {}

for i = 1, 13 do
for i = 1, 14 do
routes[i] = bp.routes:insert {
hosts = { "jwt" .. i .. ".test" },
}
Expand Down Expand Up @@ -160,6 +160,14 @@ for _, strategy in helpers.each_strategy() do
config = { anonymous = anonymous_user.username },
})

plugins:insert({
name = "jwt",
route = { id = routes[14].id },
config = {
claims_to_reject = { "mine", "yours" },
},
})

plugins:insert({
name = "ctx-checker",
route = { id = routes[1].id },
Expand Down Expand Up @@ -242,13 +250,13 @@ for _, strategy in helpers.each_strategy() do
consumer = { id = consumer15.id },
algorithm = "EdDSA",
rsa_public_key = fixtures.ed25519_public_key
}
}

rsa_jwt_secret_11 = bp.jwt_secrets:insert {
consumer = { id = consumer16.id },
algorithm = "EdDSA",
rsa_public_key = fixtures.ed448_public_key
}
}

hs_jwt_secret_1 = bp.jwt_secrets:insert {
consumer = { id = consumer7.id },
Expand Down Expand Up @@ -457,6 +465,58 @@ for _, strategy in helpers.each_strategy() do
local body = cjson.decode(assert.res_status(401, res))
assert.same({ message = "Multiple tokens provided" }, body)
end)

it("rejects claims in the claims_to_reject list", function()
-- validate we're allowed without the claims
local payload = {
iss = jwt_secret.key,
exp = os.time() + 3600,
-- yours = "1",
}
local jwt = jwt_encoder.encode(payload, jwt_secret.secret)
local res = assert(proxy_client:send {
method = "GET",
path = "/request/?jwt=" .. jwt,
headers = {
["Host"] = "jwt14.test"
}
})
assert.response(res).has.status(200)

-- validate we're not allowed with the claim "mine"
local payload = {
iss = jwt_secret.key,
exp = os.time() + 3600,
mine = "1",
}
local jwt = jwt_encoder.encode(payload, jwt_secret.secret)
local res = assert(proxy_client:send {
method = "GET",
path = "/request/?jwt=" .. jwt,
headers = {
["Host"] = "jwt14.test"
}
})
local body = assert.response(res).has.status(401)
assert.equal('{"message":"Token rejected due to claim mine"}', body)

-- validate we're not allowed with the claim "yours"
local payload = {
iss = jwt_secret.key,
exp = os.time() + 3600,
yours = "1",
}
local jwt = jwt_encoder.encode(payload, jwt_secret.secret)
local res = assert(proxy_client:send {
method = "GET",
path = "/request/?jwt=" .. jwt,
headers = {
["Host"] = "jwt14.test"
}
})
local body = assert.response(res).has.status(401)
assert.equal('{"message":"Token rejected due to claim yours"}', body)
end)
end)

describe("HS256", function()
Expand Down

0 comments on commit 097fce4

Please sign in to comment.