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

ashishtiwari 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 eed93f315 feat: add support for extra_headers in forward-auth plugin 
(#12405)
eed93f315 is described below

commit eed93f315c917e7bedcddf18d7df56af556cb918
Author: Ashish Tiwari <ashishjaitiwari15112...@gmail.com>
AuthorDate: Mon Jul 14 12:35:21 2025 +0530

    feat: add support for extra_headers in forward-auth plugin (#12405)
---
 apisix/plugins/forward-auth.lua        |  39 +++++++++++-
 docs/en/latest/plugins/forward-auth.md | 107 ++++++++++++++++++++++++++++++++-
 docs/zh/latest/plugins/forward-auth.md | 107 ++++++++++++++++++++++++++++++++-
 t/plugin/forward-auth.t                |  74 ++++++++++++++++++++++-
 4 files changed, 321 insertions(+), 6 deletions(-)

diff --git a/apisix/plugins/forward-auth.lua b/apisix/plugins/forward-auth.lua
index bd58364b2..aa220f745 100644
--- a/apisix/plugins/forward-auth.lua
+++ b/apisix/plugins/forward-auth.lua
@@ -15,9 +15,12 @@
 -- limitations under the License.
 --
 
-local ipairs = ipairs
-local core   = require("apisix.core")
-local http   = require("resty.http")
+local ipairs   = ipairs
+local core     = require("apisix.core")
+local http     = require("resty.http")
+local pairs    = pairs
+local type     = type
+local tostring = tostring
 
 local schema = {
     type = "object",
@@ -41,6 +44,20 @@ local schema = {
             items = {type = "string"},
             description = "client request header that will be sent to the 
authorization service"
         },
+        extra_headers = {
+            type = "object",
+            minProperties = 1,
+            patternProperties = {
+                ["^[^:]+$"] = {
+                    type = "string",
+                    description = "header value as a string; may contain 
variables"
+                                  .. "like $remote_addr, $request_uri"
+                }
+            },
+            description = "extra headers sent to the authorization service; "
+                        .. "values must be strings and can include variables"
+                        .. "like $remote_addr, $request_uri."
+        },
         upstream_headers = {
             type = "array",
             default = {},
@@ -102,6 +119,22 @@ function _M.access(conf, ctx)
         auth_headers["Content-Encoding"] = core.request.header(ctx, 
"content-encoding")
     end
 
+    if conf.extra_headers then
+        for header, value in pairs(conf.extra_headers) do
+            if type(value) == "number" then
+                value = tostring(value)
+            end
+            local resolve_value, err = core.utils.resolve_var(value, ctx.var)
+            if not err then
+                auth_headers[header] = resolve_value
+            end
+            if err then
+                core.log.error("failed to resolve variable in extra header '",
+                                header, "': ",value,": ",err)
+            end
+        end
+    end
+
     -- append headers that need to be get from the client request header
     if #conf.request_headers > 0 then
         for _, header in ipairs(conf.request_headers) do
diff --git a/docs/en/latest/plugins/forward-auth.md 
b/docs/en/latest/plugins/forward-auth.md
index b1aacc807..b57e92418 100644
--- a/docs/en/latest/plugins/forward-auth.md
+++ b/docs/en/latest/plugins/forward-auth.md
@@ -40,8 +40,9 @@ This Plugin moves the authentication and authorization logic 
to a dedicated exte
 | ----------------- | ------------- | -------- | ------- | -------------- | 
----------------------------------------------------------------------------------------------------------------------------------------------------------
 |
 | uri               | string        | True     |         |                | 
URI of the authorization service.                                               
                                                                           |
 | ssl_verify        | boolean       | False    | true    |                | 
When set to `true`, verifies the SSL certificate.                               
                                                                           |
-| request_method    | string        | False    | GET     | ["GET","POST"] | 
HTTP method for a client to send requests to the authorization service. When 
set to `POST` the request body is send to the authorization service.          |
+| request_method    | string        | False    | GET     | ["GET","POST"] | 
HTTP method for a client to send requests to the authorization service. When 
set to `POST` the request body is sent to the authorization service. (not 
recommended - see section on [Using data from POST 
body](#using-data-from-post-body-to-make-decision-on-authorization-service)) |
 | request_headers   | array[string] | False    |         |                | 
Client request headers to be sent to the authorization service. If not set, 
only the headers provided by APISIX are sent (for example, `X-Forwarded-XXX`). |
+| extra_headers   |object | False    |         |                | Extra 
headers to be sent to the authorization service passed in key-value format. The 
value can be a variable like `$request_uri`, `$post_arg.xyz` |
 | upstream_headers  | array[string] | False    |         |                | 
Authorization service response headers to be forwarded to the Upstream. If not 
set, no headers are forwarded to the Upstream service.                      |
 | client_headers    | array[string] | False    |         |                | 
Authorization service response headers to be sent to the client when 
authorization fails. If not set, no headers will be sent to the client.         
      |
 | timeout           | integer       | False    | 3000ms  | [1, 60000]ms   | 
Timeout for the authorization service HTTP call.                                
                                                                           |
@@ -166,6 +167,110 @@ HTTP/1.1 403 Forbidden
 Location: http://example.com/auth
 ```
 
+### Using data from POST body to make decision on Authorization service
+
+::: note
+When the decision is to be made on the basis of POST body, then it is 
recommended to use `$post_arg.*` with `extra_headers` field and make the 
decision on Authorization service on basis of headers rather than using POST 
`request_method` to pass the entire request body to Authorization service.
+:::
+
+Create a serverless function on the `/auth` route that checks for the presence 
of the `tenant_id` header. If present, the route responds with HTTP 200 and 
sets the `X-User-ID` header to a fixed value `i-am-an-user`. If `tenant_id` is 
missing, it returns HTTP 400 with an error message.
+
+```shell
+curl -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/auth' \
+    -H "X-API-KEY: $admin_key" \
+    -H 'Content-Type: application/json' \
+    -d '{
+    "uri": "/auth",
+    "plugins": {
+        "serverless-pre-function": {
+            "phase": "rewrite",
+            "functions": [
+                "return function(conf, ctx)
+                 local core = require(\"apisix.core\")
+                 if core.request.header(ctx, \"tenant_id\") then
+                     core.response.set_header(\"X-User-ID\", \"i-am-an-user\");
+                     core.response.exit(200);
+                else
+                    core.response.exit(400, \"tenant_id is required\")
+                end
+            end"
+            ]
+        }
+    }
+}'
+```
+
+Create a route that accepts POST requests and uses the `forward-auth` plugin 
to call the auth endpoint with the `tenant_id` from the request. The request is 
forwarded to the upstream service only if the auth check returns 200.
+
+```shell
+curl -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/1' \
+    -H "X-API-KEY: $admin_key" \
+    -d '{
+    "uri": "/post",
+    "methods": ["POST"],
+    "plugins": {
+        "forward-auth": {
+            "uri": "http://127.0.0.1:9080/auth";,
+            "request_method": "GET",
+            "extra_headers": {"tenant_id": "$post_arg.tenant_id"}
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "httpbin.org:80": 1
+        },
+        "type": "roundrobin"
+    }
+}'
+```
+
+Send a POST request with the `tenant_id` header:
+
+```shell
+curl -i http://127.0.0.1:9080/post -X POST -d '{
+   "tenant_id": 123
+}'
+```
+
+You should receive an `HTTP/1.1 200 OK` response similar to the following:
+
+```json
+{
+  "args": {},
+  "data": "",
+  "files": {},
+  "form": {
+    "{\n   \"tenant_id\": 123\n}": ""
+  },
+  "headers": {
+    "Accept": "*/*",
+    "Content-Length": "23",
+    "Content-Type": "application/x-www-form-urlencoded",
+    "Host": "127.0.0.1",
+    "User-Agent": "curl/8.13.0",
+    "X-Amzn-Trace-Id": "Root=1-686b6e3f-2fdeff70183e71551f5c5729",
+    "X-Forwarded-Host": "127.0.0.1"
+  },
+  "json": null,
+  "origin": "127.0.0.1, 106.215.83.33",
+  "url": "http://127.0.0.1/post";
+}
+```
+
+Send a POST request without the `tenant_id` header:
+
+```shell
+ curl -i http://127.0.0.1:9080/post -X POST -d '{
+   "abc": 123
+}'
+```
+
+You should receive an `HTTP/1.1 400 Bad Request` response with the following 
message:
+
+```shell
+tenant_id is required
+```
+
 ## Delete Plugin
 
 To remove the `forward-auth` Plugin, you can delete the corresponding JSON 
configuration from the Plugin configuration. APISIX will automatically reload 
and you do not have to restart for this to take effect.
diff --git a/docs/zh/latest/plugins/forward-auth.md 
b/docs/zh/latest/plugins/forward-auth.md
index a030a6f8f..58c462a2c 100644
--- a/docs/zh/latest/plugins/forward-auth.md
+++ b/docs/zh/latest/plugins/forward-auth.md
@@ -39,8 +39,9 @@ description: 本文介绍了关于 Apache APISIX `forward-auth` 插件的基本
 | ----------------- | ------------- | ------| ------- | -------------- | 
--------------------------------------------------------------------------------------------------------------------
 |
 | uri               | string        | 是    |         |                | 设置 
`authorization` 服务的地址 (例如:https://localhost:9188)。                              
                        |
 | ssl_verify        | boolean       | 否    | true    | [true, false]  | 当设置为 
`true` 时,验证 SSL 证书。                                                             
                     |
-| request_method    | string        | 否    | GET     | ["GET","POST"] | 客户端向 
`authorization` 服务发送请求的方法。当设置为 POST 时,会将 `request body` 转发至 `authorization` 服务。 
        |
+| request_method    | string        | 否    | GET     | ["GET","POST"] | 客户端向 
authorization 服务发送请求的方法。当设置为 POST 时,会将 request body 转发至 authorization 服务。       
  |
 | request_headers   | array[string] | 否    |         |                | 
设置需要由客户端转发到 `authorization` 服务的请求头。如果没有设置,则只发送 APISIX 提供的 headers 
(例如:X-Forwarded-XXX)。 |
+| extra_headers   |object | False    |         |                | 
以键值格式传递给授权服务的额外标头。值可以是变量,例如“$request_uri”或“$post_arg.xyz”。 |
 | upstream_headers  | array[string] | 否    |         |                | 
认证通过时,设置 `authorization` 服务转发至 `upstream` 的请求头。如果不设置则不转发任何请求头。                  
           |
 | client_headers    | array[string] | 否    |         |                | 
认证失败时,由 `authorization` 服务向 `client` 发送的响应头。如果不设置则不转发任何响应头。                     
           |
 | timeout           | integer       | 否    | 3000ms  | [1, 60000]ms   | 
`authorization` 服务请求超时时间。                                                       
                              |
@@ -168,6 +169,110 @@ HTTP/1.1 403 Forbidden
 Location: http://example.com/auth
 ```
 
+### Using data from POST body to make decision on Authorization service
+
+::: note
+当要根据 POST 正文做出决定时,建议使用带有 `extra_headers` 字段的 `$post_arg.*` 
并根据标头对授权服务做出决定,而不是使用 POST `request_method` 将整个请求正文传递给授权服务。
+:::
+
+在 `/auth` 路由上创建一个无服务器函数,用于检查 `tenant_id` 标头是否存在。如果存在,路由会使用 HTTP 200 进行响应,并将 
`X-User-ID` 标头设置为固定值 `i-am-an-user`。如果缺少 `tenant_id`,则会返回 HTTP 400 和错误消息。
+
+```shell
+curl -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/auth' \
+    -H "X-API-KEY: $admin_key" \
+    -H 'Content-Type: application/json' \
+    -d '{
+    "uri": "/auth",
+    "plugins": {
+        "serverless-pre-function": {
+            "phase": "rewrite",
+            "functions": [
+                "return function(conf, ctx)
+                 local core = require(\"apisix.core\")
+                 if core.request.header(ctx, \"tenant_id\") then
+                     core.response.set_header(\"X-User-ID\", \"i-am-an-user\");
+                     core.response.exit(200);
+                else
+                    core.response.exit(400, \"tenant_id is required\")
+                end
+            end"
+            ]
+        }
+    }
+}'
+```
+
+创建一个接受 POST 请求的路由,并使用 `forward-auth` 插件通过请求中的 `tenant_id` 调用身份验证端点。只有当身份验证检查返回 
200 时,请求才会转发到上游服务。
+
+```shell
+curl -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/1' \
+    -H "X-API-KEY: $admin_key" \
+    -d '{
+    "uri": "/post",
+    "methods": ["POST"],
+    "plugins": {
+        "forward-auth": {
+            "uri": "http://127.0.0.1:9080/auth";,
+            "request_method": "GET",
+            "extra_headers": {"tenant_id": "$post_arg.tenant_id"}
+        }
+    },
+    "upstream": {
+        "nodes": {
+            "httpbin.org:80": 1
+        },
+        "type": "roundrobin"
+    }
+}'
+```
+
+发送带有 `tenant_id` 标头的 POST 请求:
+
+```shell
+curl -i http://127.0.0.1:9080/post -X POST -d '{
+   "tenant_id": 123
+}'
+```
+
+您应该收到类似以下内容的 `HTTP/1.1 200 OK` 响应:
+
+```json
+{
+  "args": {},
+  "data": "",
+  "files": {},
+  "form": {
+    "{\n   \"tenant_id\": 123\n}": ""
+  },
+  "headers": {
+    "Accept": "*/*",
+    "Content-Length": "23",
+    "Content-Type": "application/x-www-form-urlencoded",
+    "Host": "127.0.0.1",
+    "User-Agent": "curl/8.13.0",
+    "X-Amzn-Trace-Id": "Root=1-686b6e3f-2fdeff70183e71551f5c5729",
+    "X-Forwarded-Host": "127.0.0.1"
+  },
+  "json": null,
+  "origin": "127.0.0.1, 106.215.83.33",
+  "url": "http://127.0.0.1/post";
+}
+```
+
+发送不带 `tenant_id` 标头的 POST 请求:
+
+```shell
+ curl -i http://127.0.0.1:9080/post -X POST -d '{
+   "abc": 123
+}'
+```
+
+您应该收到包含以下消息的 `HTTP/1.1 400 Bad Request` 响应:
+
+```shell
+tenant_id is required
+```
+
 ## 删除插件
 
 当你需要禁用 `forward-auth` 插件时,可以通过以下命令删除相应的 JSON 配置,APISIX 将会自动重新加载相关配置,无需重启服务:
diff --git a/t/plugin/forward-auth.t b/t/plugin/forward-auth.t
index d6f657537..ce8dd05c4 100644
--- a/t/plugin/forward-auth.t
+++ b/t/plugin/forward-auth.t
@@ -109,6 +109,17 @@ property "request_method" validation failed: matches none 
of the enum values
                                             core.response.exit(403, 
core.request.headers(ctx));
                                         end
                                     end]],
+                                    [[return function(conf, ctx)
+                                        local core = require("apisix.core");
+                                        if core.request.header(ctx, 
"Authorization") == "777" then
+                                            if core.request.header(ctx, 
"tenant_id") then
+                                                
core.response.set_header("X-User-ID", "i-am-an-user");
+                                                core.response.exit(200);
+                                            else
+                                                core.response.exit(400, 
"tenant_id is required");
+                                            end
+                                        end
+                                    end]],
                                     [[return function(conf, ctx)
                                         local core = require("apisix.core")
                                         if core.request.get_method() == "POST" 
then
@@ -274,6 +285,46 @@ property "request_method" validation failed: matches none 
of the enum values
                         "upstream_id": "u1",
                         "uri": "/onerror"
                     }]],
+                },
+                {
+                    url = "/apisix/admin/routes/9",
+                    data = [[{
+                        "plugins": {
+                            "forward-auth": {
+                                "uri": "http://127.0.0.1:1984/auth";,
+                                "request_method": "GET",
+                                "request_headers": ["Authorization"],
+                                "upstream_headers": ["X-User-ID"],
+                                "client_headers": ["Location"],
+                                "extra_headers": {"tenant_id": 
"$post_arg.tenant_id"}
+                            },
+                            "proxy-rewrite": {
+                                "uri": "/echo"
+                            }
+                        },
+                        "upstream_id": "u1",
+                        "uri": "/ping2"
+                    }]]
+                },
+                {
+                    url = "/apisix/admin/routes/10",
+                    data = [[{
+                        "plugins": {
+                            "forward-auth": {
+                                "uri": "http://127.0.0.1:1984/auth";,
+                                "request_method": "GET",
+                                "request_headers": ["Authorization"],
+                                "upstream_headers": ["X-User-ID"],
+                                "client_headers": ["Location"],
+                                "extra_headers": {"tenant_id": "abcd"}
+                            },
+                            "proxy-rewrite": {
+                                "uri": "/echo"
+                            }
+                        },
+                        "upstream_id": "u1",
+                        "uri": "/ping3"
+                    }]]
                 }
             }
 
@@ -286,7 +337,7 @@ property "request_method" validation failed: matches none 
of the enum values
         }
     }
 --- response_body eval
-"passed\n" x 10
+"passed\n" x 12
 
 
 
@@ -403,3 +454,24 @@ GET /onerror
 --- more_headers
 Authorization: 333
 --- error_code: 503
+
+
+
+=== TEST 14: hit route (test extra_headers when use post method)
+--- request
+POST /ping2
+{"tenant_id": 123}
+--- more_headers
+Authorization: 777
+--- response_body_like eval
+qr/\"x-user-id\":\"i-am-an-user\"/
+
+
+
+=== TEST 15: hit route (test extra_headers when extra headers has fixed value)
+--- request
+GET /ping3
+--- more_headers
+Authorization: 777
+--- response_body_like eval
+qr/\"x-user-id\":\"i-am-an-user\"/

Reply via email to