Skip to content
Open
52 changes: 49 additions & 3 deletions apisix/plugins/openid-connect.lua
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ local UNHANDLED_REDIRECT_URI_ERR = "unhandled request to the redirect_uri"
-- authorization callbacks
local MAX_AUTH_FLOW_RESTARTS = 3

local VAR_TEMPLATE_DESC =
"supports ${var} / ${var ?? default} templates, resolved " ..
"per-request from the request context (e.g. http_<header_name> for a " ..
"request header, or a value set by another plugin)"


-- Session config is passed as-is to resty.session.start(); the only
-- translation is the legacy session.cookie.lifetime alias from the
Expand Down Expand Up @@ -143,9 +148,18 @@ end
local schema = {
type = "object",
properties = {
client_id = {type = "string"},
client_secret = {type = "string"},
discovery = {type = "string"},
client_id = {
type = "string",
description = VAR_TEMPLATE_DESC,
},
client_secret = {
type = "string",
description = VAR_TEMPLATE_DESC,
},
discovery = {
type = "string",
description = VAR_TEMPLATE_DESC,
},
scope = {
type = "string",
default = "openid",
Expand Down Expand Up @@ -1172,8 +1186,40 @@ local function validate_claims_in_oidcauth_response(resp, conf)
end


-- discovery/client_id/client_secret accept ${var}/${var ?? default} templates so a
-- higher-priority custom plugin can select the IdP configuration per request
-- without openid-connect hard-coding that routing logic itself.
local function resolve_conf_var(ctx, field_name, value)
if type(value) ~= "string" then
return value
end

local resolved, err = core.utils.resolve_var(value, ctx.var)
if err then
return nil, "failed to resolve \"" .. field_name .. "\": " .. err
end

if resolved == nil or resolved == "" then
return nil, "resolved value of \"" .. field_name .. "\" is empty; ensure an " ..
"upstream plugin sets the request-context variable it references"
end

return resolved
end


function _M.rewrite(plugin_conf, ctx)
local conf = core.table.clone(plugin_conf)

for _, field_name in ipairs({"discovery", "client_id", "client_secret"}) do
local resolved, err = resolve_conf_var(ctx, field_name, conf[field_name])
if err then
core.log.error("openid-connect: ", err)
return 500
end
conf[field_name] = resolved
end

flatten_openidc_options(conf)

-- Snapshot the client-supplied X-Access-Token (it doubles as a bearer
Expand Down
18 changes: 18 additions & 0 deletions docs/en/latest/plugins/openid-connect.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,24 @@ And reference it in the Plugin configuration:
"client_secret": "$ENV://KEYCLOAK_CLIENT_SECRET"
```

### Per-request templating of `client_id`, `client_secret` and `discovery`

`client_id`, `client_secret`, and `discovery` support `${var}` / `${var ?? default}` templates, resolved from the request context on every request (the same templating [`limit-count`](./limit-count.md) uses for `count`/`time_window`). Only values containing `${` are treated as templates; any other value, including one with a bare `$`, is used as-is. The Plugin configuration is never modified, so each request resolves its own values.

For example, clients can send the values in request headers and reference them with `http_<header_name>`:

```json
{
"discovery": "${http_x_oidc_discovery}",
"client_id": "${http_x_oidc_client_id ?? my-default-client}",
"client_secret": "${http_x_oidc_client_secret}"
}
```

Variables can also be set by another Plugin that runs earlier, but this is not required.

If a template resolves to an empty value (for example, the header is missing and no default is given), `openid-connect` returns `500` instead of forwarding an empty value to the identity provider.

## Examples

The examples below demonstrate how you can configure the `openid-connect` Plugin for different scenarios.
Expand Down
18 changes: 18 additions & 0 deletions docs/zh/latest/plugins/openid-connect.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,24 @@ export KEYCLOAK_CLIENT_SECRET=abc
"client_secret": "$ENV://KEYCLOAK_CLIENT_SECRET"
```

### 按请求模板化 `client_id`、`client_secret` 和 `discovery`

`client_id`、`client_secret` 和 `discovery` 支持 `${var}` / `${var ?? default}` 模板,在每次请求时从请求上下文中解析(与 [`limit-count`](./limit-count.md) 用于 `count`/`time_window` 的模板机制相同)。只有包含 `${` 的值才会被视为模板,其他值(包括含有单独 `$` 的值)将按原样使用。插件配置本身不会被修改,每个请求独立解析自己的值。

例如,客户端可以通过请求头传递这些值,并使用 `http_<header_name>` 引用:

```json
{
"discovery": "${http_x_oidc_discovery}",
"client_id": "${http_x_oidc_client_id ?? my-default-client}",
"client_secret": "${http_x_oidc_client_secret}"
}
```

变量也可以由更早运行的其他插件设置,但这不是必需的。

如果模板解析为空值(例如缺少请求头且未提供默认值),`openid-connect` 将返回 `500`,而不是向身份提供商转发空值。

## 示例

以下示例展示了如何针对不同场景配置 `openid-connect` 插件。
Expand Down
Loading
Loading