Skip to content

Commit 282995f

Browse files
feat(jwt): support more algorithms (#12944)
Signed-off-by: Abhishek Choudhary <shreemaan.abhishek@gmail.com>
1 parent 9a3ef2e commit 282995f

File tree

8 files changed

+833
-63
lines changed

8 files changed

+833
-63
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,9 @@ install: runtime
391391
$(ENV_INSTALL) apisix/plugins/mcp/broker/*.lua $(ENV_INST_LUADIR)/apisix/plugins/mcp/broker
392392
$(ENV_INSTALL) apisix/plugins/mcp/transport/*.lua $(ENV_INST_LUADIR)/apisix/plugins/mcp/transport
393393

394+
$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/plugins/jwt-auth
395+
$(ENV_INSTALL) apisix/plugins/jwt-auth/*.lua $(ENV_INST_LUADIR)/apisix/plugins/jwt-auth
396+
394397
$(ENV_INSTALL) bin/apisix $(ENV_INST_BINDIR)/apisix
395398

396399

apisix/plugins/jwt-auth.lua

Lines changed: 82 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
-- limitations under the License.
1616
--
1717
local core = require("apisix.core")
18-
local jwt = require("resty.jwt")
1918
local consumer_mod = require("apisix.consumer")
2019
local new_tab = require ("table.new")
2120
local auth_utils = require("apisix.utils.auth")
@@ -27,8 +26,9 @@ local table_insert = table.insert
2726
local table_concat = table.concat
2827
local ngx_re_gmatch = ngx.re.gmatch
2928
local plugin_name = "jwt-auth"
30-
local schema_def = require("apisix.schema_def")
3129

30+
local schema_def = require("apisix.schema_def")
31+
local jwt_parser = require("apisix.plugins.jwt-auth.parser")
3232

3333
local schema = {
3434
type = "object",
@@ -60,6 +60,14 @@ local schema = {
6060
},
6161
realm = schema_def.get_realm_schema("jwt"),
6262
anonymous_consumer = schema_def.anonymous_consumer_schema,
63+
claims_to_verify = {
64+
type = "array",
65+
items = {
66+
type = "string",
67+
enum = {"exp","nbf"},
68+
},
69+
uniqueItems = true,
70+
},
6371
},
6472
}
6573

@@ -77,7 +85,21 @@ local consumer_schema = {
7785
},
7886
algorithm = {
7987
type = "string",
80-
enum = {"HS256", "HS512", "RS256", "ES256"},
88+
enum = {
89+
"HS256",
90+
"HS384",
91+
"HS512",
92+
"RS256",
93+
"RS384",
94+
"RS512",
95+
"ES256",
96+
"ES384",
97+
"ES512",
98+
"PS256",
99+
"PS384",
100+
"PS512",
101+
"EdDSA",
102+
},
81103
default = "HS256"
82104
},
83105
exp = {type = "integer", minimum = 1, default = 86400},
@@ -97,16 +119,30 @@ local consumer_schema = {
97119
{
98120
properties = {
99121
algorithm = {
100-
enum = {"HS256", "HS512"},
122+
enum = {"HS256", "HS384", "HS512"},
101123
default = "HS256"
102124
},
103125
},
104126
},
105127
{
106128
properties = {
107-
public_key = {type = "string"},
129+
public_key = {
130+
type = "string",
131+
minLength = 1,
132+
},
108133
algorithm = {
109-
enum = {"RS256", "ES256"},
134+
enum = {
135+
"RS256",
136+
"RS384",
137+
"RS512",
138+
"ES256",
139+
"ES384",
140+
"ES512",
141+
"PS256",
142+
"PS384",
143+
"PS512",
144+
"EdDSA",
145+
},
110146
},
111147
},
112148
required = {"public_key"},
@@ -141,15 +177,21 @@ function _M.check_schema(conf, schema_type)
141177
return false, err
142178
end
143179

144-
if (conf.algorithm == "HS256" or conf.algorithm == "HS512") and not conf.secret then
145-
return false, "property \"secret\" is required "..
146-
"when \"algorithm\" is \"HS256\" or \"HS512\""
147-
elseif conf.base64_secret then
180+
local is_hs_alg = conf.algorithm:sub(1, 2) == "HS"
181+
if is_hs_alg and not conf.secret then
182+
return false, "property \"secret\" is required when using HS based algorithms"
183+
end
184+
185+
if conf.base64_secret then
148186
if ngx_decode_base64(conf.secret) == nil then
149187
return false, "base64_secret required but the secret is not in base64 format"
150188
end
151189
end
152190

191+
if not is_hs_alg and not conf.public_key then
192+
return false, "missing valid public key"
193+
end
194+
153195
return true
154196
end
155197

@@ -232,15 +274,16 @@ local function get_secret(conf)
232274
return secret
233275
end
234276

235-
local function get_auth_secret(auth_conf)
236-
if not auth_conf.algorithm or auth_conf.algorithm == "HS256"
237-
or auth_conf.algorithm == "HS512" then
238-
return get_secret(auth_conf)
239-
elseif auth_conf.algorithm == "RS256" or auth_conf.algorithm == "ES256" then
240-
return auth_conf.public_key
277+
278+
local function get_auth_secret(consumer)
279+
if not consumer.auth_conf.algorithm or consumer.auth_conf.algorithm:sub(1, 2) == "HS" then
280+
return get_secret(consumer.auth_conf)
281+
else
282+
return consumer.auth_conf.public_key
241283
end
242284
end
243285

286+
244287
local function find_consumer(conf, ctx)
245288
-- fetch token and hide credentials if necessary
246289
local jwt_token, err = fetch_jwt_token(conf, ctx)
@@ -249,19 +292,19 @@ local function find_consumer(conf, ctx)
249292
return nil, nil, "Missing JWT token in request"
250293
end
251294

252-
local jwt_obj = jwt:load_jwt(jwt_token)
253-
core.log.info("jwt object: ", core.json.delay_encode(jwt_obj))
254-
if not jwt_obj.valid then
255-
err = "JWT token invalid: " .. jwt_obj.reason
295+
local jwt, err = jwt_parser.new(jwt_token)
296+
if not jwt then
297+
err = "JWT token invalid: " .. err
256298
if auth_utils.is_running_under_multi_auth(ctx) then
257299
return nil, nil, err
258300
end
259301
core.log.warn(err)
260302
return nil, nil, "JWT token invalid"
261303
end
304+
core.log.debug("parsed jwt object: ", core.json.delay_encode(jwt, true))
262305

263306
local key_claim_name = conf.key_claim_name
264-
local user_key = jwt_obj.payload and jwt_obj.payload[key_claim_name]
307+
local user_key = jwt.payload and jwt.payload[key_claim_name]
265308
if not user_key then
266309
return nil, nil, "missing user key in JWT token"
267310
end
@@ -272,7 +315,7 @@ local function find_consumer(conf, ctx)
272315
return nil, nil, "Invalid user key in JWT token"
273316
end
274317

275-
local auth_secret, err = get_auth_secret(consumer.auth_conf)
318+
local auth_secret, err = get_auth_secret(consumer)
276319
if not auth_secret then
277320
err = "failed to retrieve secrets, err: " .. err
278321
if auth_utils.is_running_under_multi_auth(ctx) then
@@ -281,23 +324,32 @@ local function find_consumer(conf, ctx)
281324
core.log.error(err)
282325
return nil, nil, "failed to verify jwt"
283326
end
284-
local claim_specs = jwt:get_default_validation_options(jwt_obj)
285-
claim_specs.lifetime_grace_period = consumer.auth_conf.lifetime_grace_period
286-
287-
jwt_obj = jwt:verify_jwt_obj(auth_secret, jwt_obj, claim_specs)
288-
core.log.info("jwt object: ", core.json.delay_encode(jwt_obj))
289327

290-
if not jwt_obj.verified then
291-
err = "failed to verify jwt: " .. jwt_obj.reason
328+
-- Now verify the JWT signature
329+
if not jwt:verify_signature(auth_secret) then
330+
local err = "failed to verify jwt: signature mismatch: " .. jwt.signature
292331
if auth_utils.is_running_under_multi_auth(ctx) then
293332
return nil, nil, err
294333
end
295334
core.log.warn(err)
296335
return nil, nil, "failed to verify jwt"
297336
end
298337

338+
-- Verify the JWT registered claims
339+
local ok, err = jwt:verify_claims(conf.claims_to_verify, {
340+
lifetime_grace_period = consumer.auth_conf.lifetime_grace_period
341+
})
342+
if not ok then
343+
err = "failed to verify jwt: " .. err
344+
if auth_utils.is_running_under_multi_auth(ctx) then
345+
return nil, nil, err
346+
end
347+
core.log.error(err)
348+
return nil, nil, "failed to verify jwt"
349+
end
350+
299351
if conf.store_in_ctx then
300-
ctx.jwt_auth_payload = jwt_obj.payload
352+
ctx.jwt_auth_payload = jwt.payload
301353
end
302354

303355
return consumer, consumer_conf

0 commit comments

Comments
 (0)