This is an automated email from the ASF dual-hosted git repository.

nic-6443 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 64cd2df78 feat(openid-connect): add set_raw_id_token_header option 
(#13616)
64cd2df78 is described below

commit 64cd2df78a83e7a4700606295444c263fdb2b282
Author: Luarx <[email protected]>
AuthorDate: Fri Jul 24 05:34:39 2026 +0200

    feat(openid-connect): add set_raw_id_token_header option (#13616)
---
 apisix/plugins/openid-connect.lua        |  24 ++++++-
 docs/en/latest/plugins/openid-connect.md |   3 +-
 t/plugin/openid-connect.t                | 110 ++++++++++++++++++++++++++++++-
 3 files changed, 134 insertions(+), 3 deletions(-)

diff --git a/apisix/plugins/openid-connect.lua 
b/apisix/plugins/openid-connect.lua
index 202cf7e2c..3cf05e0fd 100644
--- a/apisix/plugins/openid-connect.lua
+++ b/apisix/plugins/openid-connect.lua
@@ -322,6 +322,12 @@ local schema = {
             type = "boolean",
             default = true
         },
+        set_raw_id_token_header = {
+            description = "Whether the raw signed ID token JWT should be added 
in the " ..
+                "X-Raw-ID-Token header to the request for downstream.",
+            type = "boolean",
+            default = false
+        },
         set_userinfo_header = {
             description = "Whether the user info token should be added in the 
X-Userinfo " ..
                 "header to the request for downstream.",
@@ -709,7 +715,7 @@ 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
+    -- input via get_bearer_access_token) and clear the five 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")
@@ -717,6 +723,7 @@ function _M.rewrite(plugin_conf, ctx)
     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)
+    core.request.set_header(ctx, "X-Raw-ID-Token", nil)
 
     -- Previously, we multiply conf.timeout before storing it in etcd.
     -- If the timeout is too large, we should not multiply it again.
@@ -860,6 +867,15 @@ function _M.rewrite(plugin_conf, ctx)
             unauth_action = "deny"
         end
 
+        -- When set_raw_id_token_header is enabled and the user has explicitly 
restricted
+        -- session_contents, ensure enc_id_token is included so 
session:get("enc_id_token")
+        -- returns the raw signed JWT. When session_contents is nil, 
lua-resty-openidc stores
+        -- all session data by default (including enc_id_token), so no action 
is needed.
+        if conf.set_raw_id_token_header and conf.session_contents then
+            conf.session_contents = core.table.clone(conf.session_contents)
+            conf.session_contents.enc_id_token = true
+        end
+
         -- Authenticate the request. This will validate the access token if it
         -- is stored in a sessions cookie, and also renew the token if 
required.
         -- If no token can be extracted, the response will redirect to the ID
@@ -934,6 +950,12 @@ function _M.rewrite(plugin_conf, ctx)
             if refresh_token and conf.set_refresh_token_header then
                 core.request.set_header(ctx, "X-Refresh-Token", refresh_token)
             end
+
+            -- Add X-Raw-ID-Token header, maybe.
+            local enc_id_token = session:get("enc_id_token")
+            if enc_id_token and conf.set_raw_id_token_header then
+                core.request.set_header(ctx, "X-Raw-ID-Token", enc_id_token)
+            end
         end
     end
     if session then
diff --git a/docs/en/latest/plugins/openid-connect.md 
b/docs/en/latest/plugins/openid-connect.md
index 5bb74efd3..99a89945f 100644
--- a/docs/en/latest/plugins/openid-connect.md
+++ b/docs/en/latest/plugins/openid-connect.md
@@ -63,7 +63,8 @@ The `openid-connect` Plugin supports the integration with 
[OpenID Connect (OIDC)
 | token_signing_alg_values_expected | string | False | | | Algorithm used for 
signing JWT, such as `RS256`. |
 | set_access_token_header | boolean | False | true | | If true, set the access 
token in a request header. By default, the `X-Access-Token` header is used. |
 | access_token_in_authorization_header | boolean | False | false | | If true 
and if `set_access_token_header` is also true, set the access token in the 
`Authorization` header. |
-| set_id_token_header | boolean | False | true | | If true and if the ID token 
is available, set the value in the `X-ID-Token` request header. |
+| set_id_token_header | boolean | False | true | | If true and if the ID token 
is available, set the value in the `X-ID-Token` request header. Note: this 
header contains `base64(JSON(decoded_claims))` and carries no cryptographic 
signature. |
+| set_raw_id_token_header | boolean | False | false | | If true and if the raw 
signed ID token JWT is available, set the value in the `X-Raw-ID-Token` request 
header. Unlike `X-ID-Token`, this header contains the original RS256-signed JWT 
from the identity provider and can be verified against the provider's JWKS 
endpoint. The plugin automatically persists the raw JWT in the session when 
this option is enabled. |
 | set_userinfo_header | boolean | False | true | | If true and if user info 
data is available, set the value in the `X-Userinfo` request header. |
 | set_refresh_token_header | boolean | False | false | | If true and if the 
refresh token is available, set the value in the `X-Refresh-Token` request 
header. |
 | session | object | False | | | Session configuration used when `bearer_only` 
is `false` and the Plugin uses Authorization Code flow. |
diff --git a/t/plugin/openid-connect.t b/t/plugin/openid-connect.t
index 312104c67..6241884a1 100644
--- a/t/plugin/openid-connect.t
+++ b/t/plugin/openid-connect.t
@@ -937,7 +937,7 @@ OIDC introspection failed: invalid token
         }
     }
 --- response_body
-{"accept_none_alg":false,"accept_unsupported_alg":true,"access_token_expires_leeway":0,"access_token_in_authorization_header":false,"bearer_only":false,"client_id":"kbyuFDidLLm280LIwVFiazOqjO3ty8KH","client_jwt_assertion_expires_in":60,"client_secret":"60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa","discovery":"http://127.0.0.1:1980/.well-known/openid-configuration","force_reauthorize":false,"iat_slack":120,"introspection_endpoint_auth_method":"client_secret_basic","in
 [...]
+{"accept_none_alg":false,"accept_unsupported_alg":true,"access_token_expires_leeway":0,"access_token_in_authorization_header":false,"bearer_only":false,"client_id":"kbyuFDidLLm280LIwVFiazOqjO3ty8KH","client_jwt_assertion_expires_in":60,"client_secret":"60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa","discovery":"http://127.0.0.1:1980/.well-known/openid-configuration","force_reauthorize":false,"iat_slack":120,"introspection_endpoint_auth_method":"client_secret_basic","in
 [...]
 
 
 
@@ -2049,3 +2049,111 @@ passed
 --- timeout: 20
 --- response_body
 passed
+
+
+
+=== TEST 55: Configure plugin with set_raw_id_token_header enabled.
+--- 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": {
+                                "discovery": 
"http://127.0.0.1:8080/realms/University/.well-known/openid-configuration";,
+                                "realm": "University",
+                                "client_id": "course_management",
+                                "client_secret": 
"d1ec69e9-55d2-4109-a3ea-befa071579d5",
+                                "redirect_uri": "http://127.0.0.1:]] .. 
ngx.var.server_port .. [[/authenticated",
+                                "ssl_verify": false,
+                                "timeout": 10,
+                                "set_access_token_header": false,
+                                "set_id_token_header": false,
+                                "set_userinfo_header": false,
+                                "set_raw_id_token_header": true,
+                                "session": {
+                                    "secret": 
"jwcE5v3pM9VhqLxmxFOH9uZaLo8u7KQK"
+                                }
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/*"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 56: Full OIDC login sets X-Raw-ID-Token with the raw signed JWT; 
other auth headers are absent.
+--- config
+    location /t {
+        content_by_lua_block {
+            local http = require "resty.http"
+            local login_keycloak = require("lib.keycloak").login_keycloak
+            local concatenate_cookies = 
require("lib.keycloak").concatenate_cookies
+
+            local httpc = http.new()
+
+            local uri = "http://127.0.0.1:"; .. ngx.var.server_port .. "/uri"
+            local res, err = login_keycloak(uri, "[email protected]", "123456")
+            if err then
+                ngx.status = 500
+                ngx.say(err)
+                return
+            end
+
+            local cookie_str = concatenate_cookies(res.headers['Set-Cookie'])
+            local redirect_uri = "http://127.0.0.1:"; .. ngx.var.server_port .. 
res.headers['Location']
+            res, err = httpc:request_uri(redirect_uri, {
+                    method = "GET",
+                    headers = {
+                        ["Cookie"] = cookie_str
+                    }
+                })
+
+            if not res then
+                ngx.status = 500
+                ngx.say(err)
+                return
+            elseif res.status ~= 200 then
+                ngx.status = 500
+                ngx.say("Invoking the original URI didn't return the expected 
result.")
+                return
+            end
+
+            -- X-Raw-ID-Token must be present and contain a JWT (starts with 
"ey").
+            if not res.body:find("x-raw-id-token: ey", 1, true) then
+                ngx.status = 500
+                ngx.say("expected x-raw-id-token header with a JWT value, 
body: " .. res.body)
+                return
+            end
+
+            -- The other auth headers must be absent (set_*_header = false).
+            for _, unwanted in ipairs({"x-access-token:", "x-id-token:", 
"x-userinfo:"}) do
+                if res.body:find(unwanted, 1, true) then
+                    ngx.status = 500
+                    ngx.say("unexpected header found: " .. unwanted)
+                    return
+                end
+            end
+
+            ngx.say("passed")
+        }
+    }
+--- response_body
+passed

Reply via email to