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

Reply via email to