This is an automated email from the ASF dual-hosted git repository.
shreemaanabhishek 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 90a15cf6e fix(openidc): include request port in `redirect_uri` (#13081)
90a15cf6e is described below
commit 90a15cf6e611ea7c9f3471c08b8e8daf17cafaa6
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Fri Apr 3 07:02:57 2026 +0545
fix(openidc): include request port in `redirect_uri` (#13081)
---
apisix/init.lua | 9 +-
apisix/plugins/openid-connect.lua | 1 +
t/plugin/openid-connect2.t | 290 ++++++++++++++++++++++++++++++++++++++
t/plugin/openid-connect3.t | 163 +++++++++++++++++++++
4 files changed, 461 insertions(+), 2 deletions(-)
diff --git a/apisix/init.lua b/apisix/init.lua
index 5fc874254..b8ce4a11d 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -620,8 +620,10 @@ local function handle_x_forwarded_headers(api_ctx)
-- these values are observed directly by APISIX and cannot be forged,
-- making them highly credible.
local proto = api_ctx.var.scheme
- local host = api_ctx.var.host
- local port = api_ctx.var.server_port
+ local http_host = api_ctx.var.http_host or api_ctx.var.host
+ local _, port_from_host = http_host:match("^(.+):(%d+)$")
+ local host = http_host
+ local port = port_from_host or api_ctx.var.server_port
-- override the x-forwarded-* headers to the trusted ones.
-- make sure that the correct values are obtained
@@ -631,6 +633,8 @@ local function handle_x_forwarded_headers(api_ctx)
core.request.set_header(api_ctx, "X-Forwarded-Port", port)
-- later processed in ngx_tpl by `$proxy_add_x_forwarded_for`.
core.request.set_header(api_ctx, "X-Forwarded-For", nil)
+ -- Clear RFC 7239 Forwarded header to prevent forgery.
+ core.request.set_header(api_ctx, "Forwarded", nil)
-- update the cached value in http_x_forwarded_* to the trusted ones.
-- make sure that the correct values are obtained
@@ -639,6 +643,7 @@ local function handle_x_forwarded_headers(api_ctx)
api_ctx.var.http_x_forwarded_host = host
api_ctx.var.http_x_forwarded_port = port
api_ctx.var.http_x_forwarded_for = nil
+ api_ctx.var.http_forwarded = nil
end
end
diff --git a/apisix/plugins/openid-connect.lua
b/apisix/plugins/openid-connect.lua
index 9c461671c..4427caf06 100644
--- a/apisix/plugins/openid-connect.lua
+++ b/apisix/plugins/openid-connect.lua
@@ -622,6 +622,7 @@ local function validate_claims_in_oidcauth_response(resp,
conf)
return core.schema.check(conf.claim_schema, data)
end
+
function _M.rewrite(plugin_conf, ctx)
local conf_clone = core.table.clone(plugin_conf)
local conf = fetch_secrets(conf_clone, true)
diff --git a/t/plugin/openid-connect2.t b/t/plugin/openid-connect2.t
index 50ac511fd..44a4c63dc 100644
--- a/t/plugin/openid-connect2.t
+++ b/t/plugin/openid-connect2.t
@@ -711,3 +711,293 @@ property "user1" is required
--- error_code: 400
--- response_body_like
{"error_msg":"failed to check the configuration of plugin openid-connect err:
check claim_schema failed: .*: invalid JSON type: invalid_type"}
+
+
+
+=== TEST 16: untrusted source with forged X-Forwarded-Host - should use
http_host instead
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /hello
+ upstream:
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+ plugins:
+ openid-connect:
+ client_id: kbyuFDidLLm280LIwVFiazOqjO3ty8KH
+ client_secret:
60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa
+ discovery: http://127.0.0.1:1980/.well-known/openid-configuration
+ ssl_verify: false
+ timeout: 10
+ scope: apisix
+ unauth_action: auth
+ use_pkce: false
+ session:
+ secret: jwcE5v3pM9VhqLxmxFOH9uZaLo8u7KQK
+#END
+--- config
+ location /t {
+ content_by_lua_block {
+ local http = require "resty.http"
+ local httpc = http.new()
+ local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
+ local res, err = httpc:request_uri(uri, {
+ method = "GET",
+ headers = {
+ ["Host"] = "localhost:8888",
+ ["X-Forwarded-Host"] = "evil.com"
+ }
+ })
+ ngx.status = res.status
+ local location = res.headers['Location']
+ if not location then
+ ngx.say("no Location header")
+ return
+ end
+ local encoded_redirect = string.match(location,
"redirect_uri=([^&]+)")
+ if not encoded_redirect then
+ ngx.say("redirect_uri not found in Location header: " ..
location)
+ return
+ end
+ local redirect_uri = ngx.unescape_uri(encoded_redirect)
+ -- should use http_host (localhost:8888), NOT the forged
X-Forwarded-Host
+ if redirect_uri == "http://localhost:8888/hello/.apisix/redirect"
then
+ ngx.say(true)
+ else
+ ngx.say("redirect_uri mismatch: " .. redirect_uri)
+ end
+ }
+ }
+--- timeout: 10s
+--- response_body
+true
+--- error_code: 302
+
+
+
+=== TEST 17: trusted source with X-Forwarded-Host - should use X-Forwarded-Host
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+ trusted_addresses:
+ - "127.0.0.1"
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /hello
+ upstream:
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+ plugins:
+ openid-connect:
+ client_id: kbyuFDidLLm280LIwVFiazOqjO3ty8KH
+ client_secret:
60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa
+ discovery: http://127.0.0.1:1980/.well-known/openid-configuration
+ ssl_verify: false
+ timeout: 10
+ scope: apisix
+ unauth_action: auth
+ use_pkce: false
+ session:
+ secret: jwcE5v3pM9VhqLxmxFOH9uZaLo8u7KQK
+#END
+--- config
+ location /t {
+ content_by_lua_block {
+ local http = require "resty.http"
+ local httpc = http.new()
+ local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
+ local res, err = httpc:request_uri(uri, {
+ method = "GET",
+ headers = {
+ ["Host"] = "internal-host:9080",
+ ["X-Forwarded-Host"] = "public.example.com:8443",
+ ["X-Forwarded-Proto"] = "https"
+ }
+ })
+ ngx.status = res.status
+ local location = res.headers['Location']
+ if not location then
+ ngx.say("no Location header")
+ return
+ end
+ local encoded_redirect = string.match(location,
"redirect_uri=([^&]+)")
+ if not encoded_redirect then
+ ngx.say("redirect_uri parameter not found in Location header:
" .. location)
+ return
+ end
+ local redirect_uri = ngx.unescape_uri(encoded_redirect)
+ if redirect_uri ==
"https://public.example.com:8443/hello/.apisix/redirect" then
+ ngx.say(true)
+ else
+ ngx.say("redirect_uri mismatch: " .. redirect_uri)
+ end
+ }
+ }
+--- timeout: 10s
+--- response_body
+true
+--- error_code: 302
+
+
+
+=== TEST 18: trusted source with Forwarded header takes priority over
X-Forwarded-Host
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+ trusted_addresses:
+ - "127.0.0.1"
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /hello
+ upstream:
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+ plugins:
+ openid-connect:
+ client_id: kbyuFDidLLm280LIwVFiazOqjO3ty8KH
+ client_secret:
60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa
+ discovery: http://127.0.0.1:1980/.well-known/openid-configuration
+ ssl_verify: false
+ timeout: 10
+ scope: apisix
+ unauth_action: auth
+ use_pkce: false
+ session:
+ secret: jwcE5v3pM9VhqLxmxFOH9uZaLo8u7KQK
+#END
+--- config
+ location /t {
+ content_by_lua_block {
+ local http = require "resty.http"
+ local httpc = http.new()
+ local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
+ local res, err = httpc:request_uri(uri, {
+ method = "GET",
+ headers = {
+ ["Host"] = "internal-host:9080",
+ ["X-Forwarded-Host"] = "should-be-ignored.com",
+ ["X-Forwarded-Proto"] = "http",
+ ["Forwarded"] = 'host="cdn.example.com:443";proto="https"'
+ }
+ })
+ ngx.status = res.status
+ local location = res.headers['Location']
+ if not location then
+ ngx.say("no Location header")
+ return
+ end
+ local encoded_redirect = string.match(location,
"redirect_uri=([^&]+)")
+ if not encoded_redirect then
+ ngx.say("Location header does not contain redirect_uri: " ..
tostring(location))
+ return
+ end
+ local redirect_uri = ngx.unescape_uri(encoded_redirect)
+ if redirect_uri ==
"https://cdn.example.com:443/hello/.apisix/redirect" then
+ ngx.say(true)
+ else
+ ngx.say("redirect_uri mismatch: " .. redirect_uri)
+ end
+ }
+ }
+--- timeout: 10s
+--- response_body
+true
+--- error_code: 302
+
+
+
+=== TEST 19: untrusted source with forged Forwarded header - should be ignored
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /hello
+ upstream:
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+ plugins:
+ openid-connect:
+ client_id: kbyuFDidLLm280LIwVFiazOqjO3ty8KH
+ client_secret:
60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa
+ discovery: http://127.0.0.1:1980/.well-known/openid-configuration
+ ssl_verify: false
+ timeout: 10
+ scope: apisix
+ unauth_action: auth
+ use_pkce: false
+ session:
+ secret: jwcE5v3pM9VhqLxmxFOH9uZaLo8u7KQK
+#END
+--- config
+ location /t {
+ content_by_lua_block {
+ local http = require "resty.http"
+ local httpc = http.new()
+ local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
+ local res, err = httpc:request_uri(uri, {
+ method = "GET",
+ headers = {
+ ["Host"] = "localhost:8888",
+ ["Forwarded"] = 'host="evil.com";proto="https"',
+ ["X-Forwarded-Host"] = "also-evil.com"
+ }
+ })
+ ngx.status = res.status
+ local location = res.headers['Location']
+ if not location then
+ ngx.say("no Location header")
+ return
+ end
+ local encoded_redirect = string.match(location,
"redirect_uri=([^&]+)")
+ if not encoded_redirect then
+ ngx.say("redirect_uri not found in Location header: " ..
location)
+ return
+ end
+ local redirect_uri = ngx.unescape_uri(encoded_redirect)
+ -- should ignore both Forwarded and X-Forwarded-Host, use
http_host and scheme
+ if redirect_uri == "http://localhost:8888/hello/.apisix/redirect"
then
+ ngx.say(true)
+ else
+ ngx.say("redirect_uri mismatch: " .. redirect_uri)
+ end
+ }
+ }
+--- timeout: 10s
+--- response_body
+true
+--- error_code: 302
diff --git a/t/plugin/openid-connect3.t b/t/plugin/openid-connect3.t
index 6df87ae23..f9ed5bf08 100644
--- a/t/plugin/openid-connect3.t
+++ b/t/plugin/openid-connect3.t
@@ -112,3 +112,166 @@ true
--- error_code: 302
--- error_log
use http proxy
+
+
+
+=== TEST 3: Set up route with plugin matching URI `/hello` with redirect_uri
use default value.
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "openid-connect": {
+ "client_id":
"kbyuFDidLLm280LIwVFiazOqjO3ty8KH",
+ "client_secret":
"60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa",
+ "discovery":
"http://127.0.0.1:1980/.well-known/openid-configuration",
+ "ssl_verify": false,
+ "timeout": 10,
+ "scope": "apisix",
+ "unauth_action": "auth",
+ "use_pkce": false,
+ "session": {
+ "secret":
"jwcE5v3pM9VhqLxmxFOH9uZaLo8u7KQK"
+ }
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 4: The value of redirect_uri should be appended to `.apisix/redirect`
in the original request.
+--- config
+ location /t {
+ content_by_lua_block {
+ local http = require "resty.http"
+ local httpc = http.new()
+ local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
+ local res, err = httpc:request_uri(uri, {method = "GET"})
+ ngx.status = res.status
+ local location = res.headers['Location']
+ if not location then
+ ngx.say("no Location header")
+ return
+ end
+ -- extract and decode the redirect_uri from the Location header
+ local encoded_redirect = string.match(location,
"redirect_uri=([^&]+)")
+ if not encoded_redirect then
+ ngx.say("failed to extract redirect_uri from Location header:
" .. location)
+ return
+ end
+ local redirect_uri = ngx.unescape_uri(encoded_redirect)
+ local expected = uri .. "/.apisix/redirect"
+ if string.find(location, 'https://samples.auth0.com/authorize', 1,
true) and
+ string.find(location, 'scope=apisix', 1, true) and
+ string.find(location,
'client_id=kbyuFDidLLm280LIwVFiazOqjO3ty8KH', 1, true) and
+ string.find(location, 'response_type=code', 1, true) and
+ redirect_uri == expected then
+ ngx.say(true)
+ else
+ ngx.say("redirect_uri mismatch, expected: " .. expected .. ",
got: " .. redirect_uri)
+ end
+ }
+ }
+--- timeout: 10s
+--- response_body
+true
+--- error_code: 302
+
+
+
+=== TEST 5: auto-generated redirect_uri preserves non-default port from Host
header
+--- config
+ location /t {
+ content_by_lua_block {
+ local http = require "resty.http"
+ local httpc = http.new()
+ local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
+ local res, err = httpc:request_uri(uri, {
+ method = "GET",
+ headers = {
+ ["Host"] = "localhost:8888"
+ }
+ })
+ ngx.status = res.status
+ local location = res.headers['Location']
+ if not location then
+ ngx.say("no Location header")
+ return
+ end
+ local encoded_redirect = string.match(location,
"redirect_uri=([^&]+)")
+ if not encoded_redirect then
+ ngx.say("redirect_uri not found in Location header: " ..
location)
+ return
+ end
+ local redirect_uri = ngx.unescape_uri(encoded_redirect)
+ local expected = "http://localhost:8888/hello/.apisix/redirect"
+ if redirect_uri == expected then
+ ngx.say(true)
+ else
+ ngx.say("redirect_uri mismatch, expected: " .. expected .. ",
got: " .. redirect_uri)
+ end
+ }
+ }
+--- timeout: 10s
+--- response_body
+true
+--- error_code: 302
+
+
+
+=== TEST 6: auto-generated redirect_uri omits port when Host header lacks
explicit port
+--- config
+ location /t {
+ content_by_lua_block {
+ local http = require "resty.http"
+ local httpc = http.new()
+ local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello"
+ local res, err = httpc:request_uri(uri, {
+ method = "GET",
+ headers = {
+ ["Host"] = "localhost"
+ }
+ })
+ ngx.status = res.status
+ local location = res.headers['Location']
+ if not location then
+ ngx.say("no Location header")
+ return
+ end
+ local encoded_redirect = string.match(location,
"redirect_uri=([^&]+)")
+ if not encoded_redirect then
+ ngx.say("redirect_uri not found in Location header: " ..
location)
+ return
+ end
+ local redirect_uri = ngx.unescape_uri(encoded_redirect)
+ local expected = "http://localhost/hello/.apisix/redirect"
+ if redirect_uri == expected then
+ ngx.say(true)
+ else
+ ngx.say("redirect_uri mismatch, expected: " .. expected .. ",
got: " .. redirect_uri)
+ end
+ }
+ }
+--- timeout: 10s
+--- response_body
+true
+--- error_code: 302