From c094050f18581265ad7246d27925a6a30d675ec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BB=89=E6=99=93=E6=98=93?= Date: Sat, 20 Jun 2026 10:13:18 +0900 Subject: [PATCH 1/3] fix: avoid duplicate introspection client credentials --- ChangeLog | 3 +++ lib/resty/openidc.lua | 12 ++++++----- tests/spec/introspection_spec.lua | 36 ++++++++++++++++++++++++++++++- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/ChangeLog b/ChangeLog index d8078de..2b16c51 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,6 +3,9 @@ unreleased RFC 9126) through the opt-in use_par option. - propagate errors returned by the `lifecycle.on_created` hook during authorization redirects. +- don't duplicate introspection client credentials in both the + Authorization header and POST body when using client_secret_basic; + see #556 09/13/204 - cross-tenant requests are fixed with lua-resty session 4.0.x; closes #526 diff --git a/lib/resty/openidc.lua b/lib/resty/openidc.lua index fdf1dcd..bb09f11 100644 --- a/lib/resty/openidc.lua +++ b/lib/resty/openidc.lua @@ -1867,11 +1867,13 @@ function openidc.introspect(opts) body[token_param_name] = access_token - if opts.client_id then - body.client_id = opts.client_id - end - if opts.client_secret then - body.client_secret = opts.client_secret + if opts.introspection_endpoint_auth_method == nil then + if opts.client_id then + body.client_id = opts.client_id + end + if opts.client_secret then + body.client_secret = opts.client_secret + end end -- merge any provided extra parameters diff --git a/tests/spec/introspection_spec.lua b/tests/spec/introspection_spec.lua index a22a3b8..963c5cd 100644 --- a/tests/spec/introspection_spec.lua +++ b/tests/spec/introspection_spec.lua @@ -4,7 +4,12 @@ require 'busted.runner'() local function assert_introspection_endpoint_call_contains(s, case_insensitive) assert.error_log_contains("Received introspection request: .*" .. s .. ".*", - case_insensitive) + case_insensitive) +end + +local function assert_introspection_endpoint_call_doesnt_contain(s, case_insensitive) + assert.is_not.error_log_contains("Received introspection request: .*" .. s .. ".*", + case_insensitive) end describe("when the introspection endpoint is invoked", function() @@ -45,6 +50,35 @@ describe("when the introspection endpoint is invoked", function() end) end) +describe("when the introspection endpoint is invoked using client_secret_basic", function() + test_support.start_server({ + introspection_opts = { + introspection_endpoint_auth_method = "client_secret_basic" + } + }) + teardown(test_support.stop_server) + local jwt = test_support.trim(http.request("http://127.0.0.1/jwt")) + local _, status = http.request({ + url = "http://127.0.0.1/introspect", + headers = { authorization = "Bearer " .. jwt } + }) + it("the request doesn't contain the client_id parameter", function() + assert_introspection_endpoint_call_doesnt_contain("client_id=client_id") + end) + it("the request doesn't contain the client_secret parameter", function() + assert_introspection_endpoint_call_doesnt_contain("client_secret=client_secret") + end) + it("the request contains the token parameter", function() + assert_introspection_endpoint_call_contains("token=" .. jwt:gsub("%-", "%%%-")) + end) + it("the request contains a basic auth header", function() + assert.error_log_contains("introspection authorization header: Basic") + end) + it("the response is valid", function() + assert.are.equals(200, status) + end) +end) + describe("when a different token parameter name is configured", function() test_support.start_server({ introspection_opts = { From ec9be25fb23684fb72d8ff421d36f9b3401bfbcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BB=89=E6=99=93=E6=98=93?= Date: Sun, 21 Jun 2026 19:47:05 +0900 Subject: [PATCH 2/3] Deprecate implicit introspection body auth --- ChangeLog | 6 +++++- lib/resty/openidc.lua | 6 +++++- tests/spec/introspection_spec.lua | 4 ++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index e0bf5a6..dc96b04 100644 --- a/ChangeLog +++ b/ChangeLog @@ -25,7 +25,11 @@ unreleased DPoP nonce challenge. - don't duplicate introspection client credentials in both the Authorization header and POST body when using client_secret_basic; - see #556 + the legacy implicit POST body credentials behavior when + introspection_endpoint_auth_method is unset is now deprecated, so + configure introspection_endpoint_auth_method = "client_secret_post" + explicitly to keep sending credentials in the POST body in a future + release; see #556 09/13/204 - cross-tenant requests are fixed with lua-resty session 4.0.x; closes #526 diff --git a/lib/resty/openidc.lua b/lib/resty/openidc.lua index 6d92b13..3ec3a70 100644 --- a/lib/resty/openidc.lua +++ b/lib/resty/openidc.lua @@ -2234,7 +2234,11 @@ function openidc.introspect(opts) body[token_param_name] = access_token - if opts.introspection_endpoint_auth_method == nil then + local use_legacy_introspection_body_auth = opts.introspection_endpoint_auth_method == nil + if use_legacy_introspection_body_auth then + log(WARN, "introspection_endpoint_auth_method is not set; sending introspection client credentials " .. + "in the POST body is deprecated and will require explicit introspection_endpoint_auth_method = " .. + "\"client_secret_post\" in a future release") if opts.client_id then body.client_id = opts.client_id end diff --git a/tests/spec/introspection_spec.lua b/tests/spec/introspection_spec.lua index 963c5cd..3739307 100644 --- a/tests/spec/introspection_spec.lua +++ b/tests/spec/introspection_spec.lua @@ -44,6 +44,10 @@ describe("when the introspection endpoint is invoked", function() it("no cookies are sent with the introspection request", function() assert.error_log_contains("no cookie in introspection call") end) + it("the legacy body credentials behavior is deprecated", function() + assert.error_log_contains("introspection_endpoint_auth_method is not set; sending introspection client credentials " .. + "in the POST body is deprecated") + end) it("the response is valid", function() assert.are.equals(200, status) end) From eb2b2c42b9775555e88e29f26b7e5cd8a8be0d50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BB=89=E6=99=93=E6=98=93?= Date: Sat, 27 Jun 2026 11:13:11 +0900 Subject: [PATCH 3/3] Avoid repeated introspection deprecation warnings --- lib/resty/openidc.lua | 11 ++++-- tests/spec/introspection_spec.lua | 63 ++++++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/lib/resty/openidc.lua b/lib/resty/openidc.lua index 3ec3a70..a68d394 100644 --- a/lib/resty/openidc.lua +++ b/lib/resty/openidc.lua @@ -60,6 +60,8 @@ local DEBUG = ngx.DEBUG local ERROR = ngx.ERR local WARN = ngx.WARN +local has_logged_legacy_introspection_body_auth_warning = false + local function token_auth_method_precondition(method, required_field) return function(opts) if not opts[required_field] then @@ -2236,9 +2238,12 @@ function openidc.introspect(opts) local use_legacy_introspection_body_auth = opts.introspection_endpoint_auth_method == nil if use_legacy_introspection_body_auth then - log(WARN, "introspection_endpoint_auth_method is not set; sending introspection client credentials " .. - "in the POST body is deprecated and will require explicit introspection_endpoint_auth_method = " .. - "\"client_secret_post\" in a future release") + if (opts.client_id or opts.client_secret) and not has_logged_legacy_introspection_body_auth_warning then + log(WARN, "introspection_endpoint_auth_method is not set; sending introspection client credentials " .. + "in the POST body is deprecated and will require explicit introspection_endpoint_auth_method = " .. + "\"client_secret_post\" in a future release") + has_logged_legacy_introspection_body_auth_warning = true + end if opts.client_id then body.client_id = opts.client_id end diff --git a/tests/spec/introspection_spec.lua b/tests/spec/introspection_spec.lua index 3739307..695bde6 100644 --- a/tests/spec/introspection_spec.lua +++ b/tests/spec/introspection_spec.lua @@ -12,6 +12,23 @@ local function assert_introspection_endpoint_call_doesnt_contain(s, case_insensi case_insensitive) end +local function error_log_occurrences(s) + local count = 0 + local pos = 1 + local log = test_support.load("/tmp/server/logs/error.log") + while true do + pos = log:find(s, pos, true) + if not pos then + return count + end + count = count + 1 + pos = pos + #s + end +end + +local legacy_introspection_body_auth_warning = "introspection_endpoint_auth_method is not set; " .. + "sending introspection client credentials in the POST body is deprecated" + describe("when the introspection endpoint is invoked", function() test_support.start_server() teardown(test_support.stop_server) @@ -45,8 +62,7 @@ describe("when the introspection endpoint is invoked", function() assert.error_log_contains("no cookie in introspection call") end) it("the legacy body credentials behavior is deprecated", function() - assert.error_log_contains("introspection_endpoint_auth_method is not set; sending introspection client credentials " .. - "in the POST body is deprecated") + assert.error_log_contains(legacy_introspection_body_auth_warning) end) it("the response is valid", function() assert.are.equals(200, status) @@ -54,6 +70,49 @@ describe("when the introspection endpoint is invoked", function() end) end) +describe("when legacy introspection body credentials are used without caching", function() + test_support.start_server({ + introspection_opts = { + introspection_cache_ignore = true + } + }) + teardown(test_support.stop_server) + local jwt = test_support.trim(http.request("http://127.0.0.1/jwt")) + local _, first_status = http.request({ + url = "http://127.0.0.1/introspect", + headers = { authorization = "Bearer " .. jwt } + }) + local _, second_status = http.request({ + url = "http://127.0.0.1/introspect", + headers = { authorization = "Bearer " .. jwt } + }) + it("logs the deprecation warning only once", function() + assert.are.equals(1, error_log_occurrences(legacy_introspection_body_auth_warning)) + end) + it("the responses are valid", function() + assert.are.equals(200, first_status) + assert.are.equals(200, second_status) + end) +end) + +describe("when legacy introspection body auth is used without client credentials", function() + test_support.start_server({ + remove_introspection_config_keys = { "client_id", "client_secret" } + }) + teardown(test_support.stop_server) + local jwt = test_support.trim(http.request("http://127.0.0.1/jwt")) + local _, status = http.request({ + url = "http://127.0.0.1/introspect", + headers = { authorization = "Bearer " .. jwt } + }) + it("doesn't log the deprecation warning", function() + assert.is_not.error_log_contains(legacy_introspection_body_auth_warning) + end) + it("the response is valid", function() + assert.are.equals(200, status) + end) +end) + describe("when the introspection endpoint is invoked using client_secret_basic", function() test_support.start_server({ introspection_opts = {