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 844297734 fix(openid-connect): ensure identity headers reflect
validated tokens (#13330)
844297734 is described below
commit 844297734fe481d3c6b6399b134a311b79e8d92f
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Fri May 8 09:22:58 2026 +0800
fix(openid-connect): ensure identity headers reflect validated tokens
(#13330)
The plugin only set X-Access-Token / X-Userinfo / X-ID-Token /
X-Refresh-Token when they were not already present in the incoming
request, and never cleared client-supplied values for these reserved
header names at the start of the request. This made the headers the
upstream received depend on what the client sent rather than purely on
what the plugin validated.
This change clears the four headers at the start of rewrite (snapshotting
the X-Access-Token value first since it doubles as a bearer-token input
to get_bearer_access_token), and makes add_access_token_header always
overwrite, so the upstream consistently sees plugin-controlled values.
---
apisix/plugins/openid-connect.lua | 31 +++---
t/plugin/openid-connect-identity-headers.t | 168 +++++++++++++++++++++++++++++
2 files changed, 184 insertions(+), 15 deletions(-)
diff --git a/apisix/plugins/openid-connect.lua
b/apisix/plugins/openid-connect.lua
index 1ebc12dec..3ecf9d246 100644
--- a/apisix/plugins/openid-connect.lua
+++ b/apisix/plugins/openid-connect.lua
@@ -448,7 +448,7 @@ local function get_bearer_access_token(ctx)
local auth_header = core.request.header(ctx, "Authorization")
if not auth_header then
-- No Authorization header, get X-Access-Token header, maybe.
- local access_token_header = core.request.header(ctx, "X-Access-Token")
+ local access_token_header = ctx.openid_connect_client_x_access_token
if not access_token_header then
-- No X-Access-Token header neither.
return false, nil, nil
@@ -573,20 +573,11 @@ end
local function add_access_token_header(ctx, conf, token)
- if token then
- -- Add Authorization or X-Access-Token header, respectively, if not
already set.
- if conf.set_access_token_header then
- if conf.access_token_in_authorization_header then
- if not core.request.header(ctx, "Authorization") then
- -- Add Authorization header.
- core.request.set_header(ctx, "Authorization", "Bearer " ..
token)
- end
- else
- if not core.request.header(ctx, "X-Access-Token") then
- -- Add X-Access-Token header.
- core.request.set_header(ctx, "X-Access-Token", token)
- end
- end
+ if token and conf.set_access_token_header then
+ if conf.access_token_in_authorization_header then
+ core.request.set_header(ctx, "Authorization", "Bearer " .. token)
+ else
+ core.request.set_header(ctx, "X-Access-Token", token)
end
end
end
@@ -626,6 +617,16 @@ end
function _M.rewrite(plugin_conf, ctx)
local conf = core.table.clone(plugin_conf)
+ -- Snapshot the client-supplied X-Access-Token (it doubles as a bearer
+ -- input via get_bearer_access_token) and clear the four headers this
+ -- plugin advertises as outputs so client-supplied values cannot bleed
+ -- through to the upstream.
+ ctx.openid_connect_client_x_access_token = core.request.header(ctx,
"X-Access-Token")
+ core.request.set_header(ctx, "X-Access-Token", nil)
+ core.request.set_header(ctx, "X-Userinfo", nil)
+ core.request.set_header(ctx, "X-ID-Token", nil)
+ core.request.set_header(ctx, "X-Refresh-Token", nil)
+
-- Previously, we multiply conf.timeout before storing it in etcd.
-- If the timeout is too large, we should not multiply it again.
if not (conf.timeout >= 1000 and conf.timeout % 1000 == 0) then
diff --git a/t/plugin/openid-connect-identity-headers.t
b/t/plugin/openid-connect-identity-headers.t
new file mode 100644
index 000000000..ca842c827
--- /dev/null
+++ b/t/plugin/openid-connect-identity-headers.t
@@ -0,0 +1,168 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+
+add_block_preprocessor(sub {
+ my ($block) = @_;
+
+ if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+ $block->set_value("no_error_log", "[error]");
+ }
+
+ if (!defined $block->request) {
+ $block->set_value("request", "GET /t");
+ }
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: Set up route with unauth_action = "pass" so unauthenticated
requests pass through.
+--- 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":
"https://samples.auth0.com/.well-known/openid-configuration",
+ "redirect_uri": "https://iresty.com",
+ "ssl_verify": false,
+ "timeout": 10,
+ "scope": "apisix",
+ "unauth_action": "pass",
+ "use_pkce": false,
+ "session": {
+ "secret":
"jwcE5v3pM9VhqLxmxFOH9uZaLo8u7KQK"
+ }
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/uri"
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 2: Access route without authentication. Client-supplied X-Userinfo /
X-ID-Token / X-Refresh-Token / X-Access-Token must not reach upstream.
+--- request
+GET /uri HTTP/1.1
+--- more_headers
+X-Userinfo: client-supplied-userinfo
+X-ID-Token: client-supplied-id-token
+X-Refresh-Token: client-supplied-refresh-token
+X-Access-Token: client-supplied-access-token
+--- response_body
+uri: /uri
+host: localhost
+x-real-ip: 127.0.0.1
+--- error_code: 200
+
+
+
+=== TEST 3: Update route to bearer_only with embedded RS256 public key.
+--- 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":
"https://samples.auth0.com/.well-known/openid-configuration",
+ "redirect_uri": "https://iresty.com",
+ "ssl_verify": false,
+ "timeout": 10,
+ "bearer_only": true,
+ "scope": "apisix",
+ "public_key": "-----BEGIN PUBLIC KEY-----\n]]
..
+
[[MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw86xcJwNxL2MkWnjIGiw\n]] ..
+
[[94QY78Sq89dLqMdV/Ku2GIX9lYkbS0VDGtmxDGJLBOYW4cKTX+pigJyzglLgE+nD\n]] ..
+
[[z3VJf2oCqSV74gTyEdi7sw9e1rCyR6dR8VA7LEpIHwmhnDhhjXy1IYSKRdiVHLS5\n]] ..
+
[[sYmaAGckpUo3MLqUrgydGj5tFzvK/R/ELuZBdlZM+XuWxYry05r860E3uL+VdVCO\n]] ..
+
[[oU4RJQknlJnTRd7ht8KKcZb6uM14C057i26zX/xnOJpaVflA4EyEo99hKQAdr8Sh\n]] ..
+
[[G70MOLYvGCZxl1o8S3q4X67MxcPlfJaXnbog2AOOGRaFar88XiLFWTbXMCLuz7xD\n]] ..
+ [[zQIDAQAB\n]] ..
+ [[-----END PUBLIC KEY-----",
+ "token_signing_alg_values_expected": "RS256",
+ "set_id_token_header": false,
+ "set_userinfo_header": false,
+ "claim_validator": {
+ "issuer": {
+ "valid_issuers": ["Mysoft corp"]
+ }
+ }
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/uri"
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 4: Access route with valid bearer token and a client-supplied
X-Access-Token. Upstream must see the validated token, not the client value.
+--- request
+GET /uri HTTP/1.1
+--- more_headers
+Authorization: Bearer
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.Vq_sBN7nH67vMDbiJE01EP4hvJYE_5ju6izjkOX8pF5OS4g2RWKWpL6h6-b0tTkCzG4JD5BEl13LWW-Gxxw0i9vEK0FLg_kC_kZLYB8WuQ6B9B9YwzmZ3OLbgnYzt_VD7D-7psEbwapJl5hbFsIjDgOAEx-UCmjUcl2frZxZavG2LUiEGs9Ri7KqOZmTLgNDMWfeWh1t1LyD0_b-eTInbasVtKQxMlb5kR0Ln_Qg5092L-irJ7dqaZma7HItCnzXJROdqJEsMIBA
[...]
+X-Access-Token: client-supplied-access-token
+--- response_body
+uri: /uri
+authorization: Bearer
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.Vq_sBN7nH67vMDbiJE01EP4hvJYE_5ju6izjkOX8pF5OS4g2RWKWpL6h6-b0tTkCzG4JD5BEl13LWW-Gxxw0i9vEK0FLg_kC_kZLYB8WuQ6B9B9YwzmZ3OLbgnYzt_VD7D-7psEbwapJl5hbFsIjDgOAEx-UCmjUcl2frZxZavG2LUiEGs9Ri7KqOZmTLgNDMWfeWh1t1LyD0_b-eTInbasVtKQxMlb5kR0Ln_Qg5092L-irJ7dqaZma7HItCnzXJROdqJEsMIBA
[...]
+host: localhost
+x-access-token:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhMSI6IkRhdGEgMSIsImlhdCI6MTU4NTEyMjUwMiwiZXhwIjoxOTAwNjk4NTAyLCJhdWQiOiJodHRwOi8vbXlzb2Z0Y29ycC5pbiIsImlzcyI6Ik15c29mdCBjb3JwIiwic3ViIjoic29tZUB1c2VyLmNvbSJ9.Vq_sBN7nH67vMDbiJE01EP4hvJYE_5ju6izjkOX8pF5OS4g2RWKWpL6h6-b0tTkCzG4JD5BEl13LWW-Gxxw0i9vEK0FLg_kC_kZLYB8WuQ6B9B9YwzmZ3OLbgnYzt_VD7D-7psEbwapJl5hbFsIjDgOAEx-UCmjUcl2frZxZavG2LUiEGs9Ri7KqOZmTLgNDMWfeWh1t1LyD0_b-eTInbasVtKQxMlb5kR0Ln_Qg5092L-irJ7dqaZma7HItCnzXJROdqJEsMIBAYRwDGa
[...]
+x-real-ip: 127.0.0.1
+--- error_code: 200