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]

Reply via email to