Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
afa520d
Fix #11276 + little style refactor
mikyll May 23, 2024
afb3d6b
Update jwt-auth.md
mikyll May 23, 2024
b88cf9d
infra: Increase PR reviewers to 3 when merge to master. (#11280)
moonming May 24, 2024
55eeb6b
docs: correct the default collector config apisix actually used for o…
flearc May 30, 2024
c2ba478
docs: added Write a Review link (#11313)
juzhiyuan Jun 3, 2024
08cb3ad
docs: add http3 docs (#11302)
kayx23 Jun 3, 2024
cf84292
feat: move tinyyaml to lyaml (#11312)
bzp2010 Jun 3, 2024
d106de5
fix(ssl): ssl key rotation caused request failure (#11305)
AlinsRan Jun 3, 2024
bd76c43
Revert style changes to jwt-auth plugin
mikyll Jun 4, 2024
2af7e15
Added test case for new jwt-auth feature
mikyll Jun 4, 2024
92bd025
Update jwt-auth4.t
mikyll Jun 4, 2024
d77d672
fix: add libyaml-dev dependency for apt. (#11291)
adam-huganir Jun 5, 2024
953be46
fix: after updating the header, get the old value from the ctx.var (#…
AlinsRan Jun 6, 2024
0cacb90
docs: add plugin config to standalone deployment doc (#11332)
kayx23 Jun 6, 2024
fec3137
docs: add http/3 in README.md. (#11318)
moonming Jun 7, 2024
3ad9c28
build(undeps): remove all rocks before remove openresty (#11333)
flearc Jun 11, 2024
5e383e0
feat(secret): support store ssl.keys ssl.certs in secrets mamager (#1…
AlinsRan Jun 11, 2024
1a45d1d
fix(datadog): report consumer username tag (#11354)
bzp2010 Jun 17, 2024
de1669d
docs: improve debug mode yaml comments (#11373)
kayx23 Jun 26, 2024
6d2de7e
fix: make the message clearer when API key is missing (#11370)
pottekkat Jun 26, 2024
4dbecfd
docs: add http-dubbo docs (#11322)
ShenFeng312 Jul 3, 2024
7f649bc
ci: removed centos, chaos, fuzzing and fips CIs. (#11394)
moonming Jul 8, 2024
1164374
fix(grpc-transcode): filter out illegal INT(string) formats (#11367)
zhoujiexiong Jul 9, 2024
667ac7f
Fix #11276 + little style refactor
mikyll May 23, 2024
b2ed6b8
Update jwt-auth.md
mikyll May 23, 2024
c944947
Revert style changes to jwt-auth plugin
mikyll Jun 4, 2024
1ddbc8f
Added test case for new jwt-auth feature
mikyll Jun 4, 2024
76816f1
Update jwt-auth4.t
mikyll Jun 4, 2024
1203a5a
Merge branch 'jwt_key_claim_name' of https://github.com/mikyll/apisix…
mikyll Sep 20, 2024
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
25 changes: 16 additions & 9 deletions apisix/plugins/jwt-auth.lua
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ local schema = {
hide_credentials = {
type = "boolean",
default = false
},
key_claim_name = {
type = "string",
default = "key"
}
},
}
Expand Down Expand Up @@ -247,9 +251,9 @@ local function get_rsa_or_ecdsa_keypair(conf)
end


local function get_real_payload(key, auth_conf, payload)
local function get_real_payload(key, auth_conf, payload, key_claim_name)
local real_payload = {
key = key,
[key_claim_name] = key,
Copy link
Contributor

Choose a reason for hiding this comment

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

although the key_claim_name has changed, the value of key_claim_name will always be key. That doesn't fit into the scene. For example, if the key_claim_name is exp then the value should be the expiry time, not the key. Right?

Copy link
Contributor Author

@mikyll mikyll Sep 22, 2024

Choose a reason for hiding this comment

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

@shreemaan-abhishek I'm not sure if I understood your comment correctly, in case I didn't please correct me :)


The purpose of key_claim_name parameter is just to tell in which claim the key is stored, so that the plugin can use this for the validation. For example, the company where I work uses JWTs which do not include the key claim, but do include iss instead.

The reason why I also made changes to get_real_payload() and the like, was to provide a way to generate a signed token with a custom key claim name, through the public API (otherwise I would have needed to make a test case with a hardcoded token, see line 182 of commit 2af7e156aa07ca29cf19f4934daa605eb0571902 for reference).

Addressing your comment, if key_claim_name was "exp", then no, the value shouldn't be the expiry time, but still the key. However, that would cause some problem:

  • even though according to JWT standard, RFC 7519, the exp claim is optional, the jwt-auth plugin requires it to validate the token, and returns 401 if it's not present (at this line):
    [warn] 64#64: *16957 [lua] jwt-auth.lua:442: phase_func(): failed to verify jwt: Missing one of claims - [ nbf, exp ].,
  • setting key_claim_name to "exp" is not feasible, since that would generate a token with duplicated exp claim.

At most, we could handle this case by preventing the usage of "exp" and "nbf" as key_claim_name, what do you think?

exp = ngx_time() + auth_conf.exp
}
if payload then
Expand All @@ -261,7 +265,7 @@ local function get_real_payload(key, auth_conf, payload)
end


local function sign_jwt_with_HS(key, consumer, payload)
local function sign_jwt_with_HS(key, consumer, payload, key_claim_name)
local auth_secret, err = get_secret(consumer.auth_conf)
if not auth_secret then
core.log.error("failed to sign jwt, err: ", err)
Expand All @@ -274,7 +278,7 @@ local function sign_jwt_with_HS(key, consumer, payload)
typ = "JWT",
alg = consumer.auth_conf.algorithm
},
payload = get_real_payload(key, consumer.auth_conf, payload)
payload = get_real_payload(key, consumer.auth_conf, payload, key_claim_name)
}
)
if not ok then
Expand All @@ -285,7 +289,7 @@ local function sign_jwt_with_HS(key, consumer, payload)
end


local function sign_jwt_with_RS256_ES256(key, consumer, payload)
local function sign_jwt_with_RS256_ES256(key, consumer, payload, key_claim_name)
local public_key, private_key, err = get_rsa_or_ecdsa_keypair(
consumer.auth_conf
)
Expand All @@ -304,7 +308,7 @@ local function sign_jwt_with_RS256_ES256(key, consumer, payload)
public_key,
}
},
payload = get_real_payload(key, consumer.auth_conf, payload)
payload = get_real_payload(key, consumer.auth_conf, payload, key_claim_name)
}
)
if not ok then
Expand Down Expand Up @@ -348,9 +352,10 @@ function _M.rewrite(conf, ctx)
return 401, {message = "JWT token invalid"}
end

local user_key = jwt_obj.payload and jwt_obj.payload.key
local key_claim_name = conf.key_claim_name
local user_key = jwt_obj.payload and jwt_obj.payload[key_claim_name]
if not user_key then
return 401, {message = "missing user key in JWT token"}
return 401, {message = "missing " .. key_claim_name .. " claim in JWT token"}
end

local consumer_conf = consumer_mod.plugin(plugin_name)
Expand Down Expand Up @@ -395,6 +400,8 @@ local function gen_token()

local key = args.key
local payload = args.payload
local key_claim_name = args.key_claim_name or "key"

if payload then
payload = ngx.unescape_uri(payload)
end
Expand All @@ -415,7 +422,7 @@ local function gen_token()
core.log.info("consumer: ", core.json.delay_encode(consumer))

local sign_handler = algorithm_handler(consumer, true)
local jwt_token = sign_handler(key, consumer, payload)
local jwt_token = sign_handler(key, consumer, payload, key_claim_name)
if jwt_token then
return core.response.exit(200, jwt_token)
end
Expand Down
3 changes: 2 additions & 1 deletion docs/en/latest/plugins/jwt-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ For Route:
| header | string | False | authorization | The header to get the token from. |
| query | string | False | jwt | The query string to get the token from. Lower priority than header. |
| cookie | string | False | jwt | The cookie to get the token from. Lower priority than query. |
| hide_credentials | boolean | False | false | Set to true will not pass the authorization request of header\query\cookie to the Upstream.|
| hide_credentials | boolean | False | false | Set to true will not pass the authorization request of header\query\cookie to the Upstream. |
| key_claim_name | string | False | key | The name of the JWT claim that contains the user key (corresponds to Consumer's key attribute). |

You can implement `jwt-auth` with [HashiCorp Vault](https://www.vaultproject.io/) to store and fetch secrets and RSA keys pairs from its [encrypted KV engine](https://developer.hashicorp.com/vault/docs/secrets/kv) using the [APISIX Secret](../terminology/secret.md) resource.

Expand Down
97 changes: 97 additions & 0 deletions t/plugin/jwt-auth4.t
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,100 @@ __DATA__
}
--- response_body
safe-jws



=== TEST 2: enable jwt auth plugin (with custom key_claim_name) using admin api
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/4',
ngx.HTTP_PUT,
[[{
"plugins": {
"jwt-auth": {
"key": "custom-user-key",
"secret": "custom-secret-key",
"key_claim_name": "iss"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)

if code >= 300 then
ngx.status = code
ngx.say(body)
return
end

ngx.say(body)
}
}
--- response_body
passed



=== TEST 3: verify that key_claim_name can be used to validate the Consumer JWT
with a different claim than 'key'
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test

-- prepare consumer with a custom key claim name
local csm_code, csm_body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "mike",
"plugins": {
"jwt-auth": {
"key": "custom-user-key",
"secret": "custom-secret-key"
}
}
}]]
)

if csm_code >= 300 then
ngx.status = csm_code
ngx.say(csm_body)
return
end

-- generate JWT with custom key ("key_claim_name" = "iss")
local sign_code, sign_body, token = t('/apisix/plugin/jwt/sign?key=custom-user-key&key_claim_name=iss',
ngx.HTTP_GET
)

if sign_code > 200 then
ngx.status = sign_code
ngx.say(sign_body)
return
end

-- verify JWT using the custom key_claim_name
local ver_code, ver_body = t('/hello?jwt=' .. token,
ngx.HTTP_GET
)

if ver_code > 200 then
ngx.status = ver_code
ngx.say(ver_body)
return
end

ngx.say("verified-jwt")
}
}
--- response_body
verified-jwt