This is an automated email from the ASF dual-hosted git repository.
shreemaan-abhishek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 638a87929 fix(jwt-auth): reject malformed JWT signature instead of
erroring (#13518)
638a87929 is described below
commit 638a87929210d727bf4f9bdc721180838eb6185f
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Mon Jun 15 11:19:17 2026 +0800
fix(jwt-auth): reject malformed JWT signature instead of erroring (#13518)
---
apisix/plugins/jwt-auth/parser.lua | 25 +++++++++++++--
t/plugin/jwt-auth.t | 62 ++++++++++++++++++++++++++++++++++++++
2 files changed, 85 insertions(+), 2 deletions(-)
diff --git a/apisix/plugins/jwt-auth/parser.lua
b/apisix/plugins/jwt-auth/parser.lua
index 098a26825..0d2be383c 100644
--- a/apisix/plugins/jwt-auth/parser.lua
+++ b/apisix/plugins/jwt-auth/parser.lua
@@ -32,6 +32,7 @@ local ipairs = ipairs
local type = type
local error = error
local pcall = pcall
+local tostring = tostring
local default_claims = {
"nbf",
@@ -223,8 +224,28 @@ end
function _M.verify_signature(self, key)
- return alg_verify[self.header.alg](self.raw_header .. "." ..
- self.raw_payload, base64_decode(self.signature), key)
+ local verifier = alg_verify[self.header.alg]
+ if not verifier then
+ return false, "unsupported algorithm: " .. tostring(self.header.alg)
+ end
+
+ local signature = base64_decode(self.signature)
+ if not signature then
+ return false, "failed to decode signature"
+ end
+
+ -- the per-algorithm verifiers assert on signature length and key validity,
+ -- so guard with pcall to turn a malformed token into a clean rejection
+ -- instead of letting the error propagate as a 500 response
+ local ok, verified, verify_err = pcall(verifier,
+ self.raw_header .. "." .. self.raw_payload, signature, key)
+ if not ok then
+ -- verifier raised: `verified` holds the caught error message
+ return false, verified
+ end
+
+ -- preserve the verifier's own (verified, err) return contract
+ return verified, verify_err
end
diff --git a/t/plugin/jwt-auth.t b/t/plugin/jwt-auth.t
index f9cb66ec4..29eb6b869 100644
--- a/t/plugin/jwt-auth.t
+++ b/t/plugin/jwt-auth.t
@@ -1428,3 +1428,65 @@ GET
/hello?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4
{"message":"failed to verify jwt"}
--- error_log
failed to verify jwt: 'exp' claim expired at Tue, 23 Jul 2019 08:28:21 GMT
+
+
+
+=== TEST 59: malformed signatures must be rejected with 401, not crash with 500
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ -- ES256 consumer: the per-algorithm verifier asserts the signature
+ -- length and decodes it from base64url, so a malformed signature
+ -- used to raise a Lua error and surface as a 500 instead of a 401.
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "kerouac",
+ "plugins": {
+ "jwt-auth": {
+ "key": "user-key-es256",
+ "algorithm": "ES256",
+ "public_key": "-----BEGIN PUBLIC
KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9\nq9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==\n-----END
PUBLIC KEY-----"
+ }
+ }
+ }]]
+ )
+ assert(code < 300, body)
+
+ code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "jwt-auth": {}
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+ assert(code < 300, body)
+
+ -- valid ES256 header + payload (key claim = user-key-es256), with
two
+ -- malformed signatures: "YWJj" decodes to 3 bytes (not the
required
+ -- 64), and "@@@@" is not valid base64url so decoding returns nil
+ local header_payload = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9"
+ .. ".eyJrZXkiOiJ1c2VyLWtleS1lczI1NiIsIm5iZiI6MTcyNzI3NDk4M30"
+ for _, sig in ipairs({"YWJj", "@@@@"}) do
+ local rc, rb = t('/hello?jwt=' .. header_payload .. "." .. sig,
+ ngx.HTTP_GET)
+ assert(rc == 401, "signature '" .. sig .. "' expected 401 but
got "
+ .. tostring(rc))
+ assert(string.find(rb, "failed to verify jwt", 1, true), rb)
+ end
+ ngx.say("passed")
+ }
+ }
+--- response_body
+passed
+--- no_error_log
+[error]