Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
0ee480e
feat(openid-connect): support stateless session revocation
Hockenba Aug 27, 2026
cf2cc5e
refactor(openid-connect): expose session revocation backend
Hockenba Aug 28, 2026
7572d22
fix(openid-connect): avoid unconditional revocation default
Hockenba Aug 28, 2026
0c3c35c
for lua-resty-openidc changes
Hockenba Sep 2, 2026
cac900b
update to newest openidc with my commit
Hockenba Sep 5, 2026
a56f816
Merge apache/master into feat/openid-connect-stateless-session-revoca…
Hockenba Sep 7, 2026
e9022c1
in memory cache config options
Hockenba Sep 15, 2026
5c1b46d
Merge branch 'master' of https://github.com/apache/apisix into feat/o…
Hockenba Sep 18, 2026
56083de
chore(deps): bump lua-resty-session to 4.3.0-1
Hockenba Nov 9, 2026
df39083
Merge branch 'master' of https://github.com/apache/apisix into feat/o…
Hockenba Nov 9, 2026
b420af1
fix(openid-connect): disable revocation error cache with the lookup c…
Hockenba Nov 9, 2026
ddcf668
test(openid-connect): cover revocation cache TTL normalization
Hockenba Nov 9, 2026
6afeda5
test(openid-connect): move revocation TTL test into the revocation suite
Hockenba Nov 9, 2026
07ecd1f
docs(openid-connect): explain the revocation lookup cache
Hockenba Nov 9, 2026
f494d1e
chore(openid-connect): drop the library version from a comment
Hockenba Nov 9, 2026
ad249f3
fix(openid-connect): override the revocation error TTL only where it …
Hockenba Nov 9, 2026
5ecfa78
chore(openid-connect): reword the revocation error TTL comments
Hockenba Nov 9, 2026
dfccf89
test(openid-connect): open the cached session in a separate request
Hockenba Nov 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apisix-master-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ dependencies = {
"api7-lua-resty-jwt = 0.2.6-0",
"lua-resty-hmac-ffi = 0.06-1",
"lua-resty-cookie = 0.4.1-1",
"lua-resty-session = 4.2.0-1",
"lua-resty-session = 4.3.0-1",
"lua-resty-openapi-validator = 1.0.6-1",
"opentracing-openresty = 0.1-0",
"lua-resty-radixtree = 2.9.2-0",
Expand Down
68 changes: 66 additions & 2 deletions apisix/plugins/openid-connect.lua
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ local MAX_AUTH_FLOW_RESTARTS = 3
-- Session config is passed as-is to resty.session.start(); the only
-- translation is the legacy session.cookie.lifetime alias from the
-- lua-resty-session 3.x schema, which is mapped to absolute_timeout
-- when the latter is unset.
-- when the latter is unset. The revocation error cache needs the lookup cache:
-- the session library only allocates its cache when revocation_cache_ttl > 0,
-- so with the lookup cache off the error TTL is set to 0 as well.
local function build_session_opts(session_conf)
if not session_conf then
return nil
Expand All @@ -68,6 +70,18 @@ local function build_session_opts(session_conf)
"use session.absolute_timeout instead")
end
end
if session_conf.revocation and session_conf.revocation_cache_ttl == 0 then
local error_ttl = session_conf.revocation_error_cache_ttl
-- the error cache needs the lookup cache, so a non-zero error TTL, set
-- or defaulted by the library, cannot apply without it
if error_ttl == nil or error_ttl > 0 then
if error_ttl then
core.log.warn("session.revocation_error_cache_ttl is ignored when ",
"session.revocation_cache_ttl is 0")
end
session_conf.revocation_error_cache_ttl = 0
end
end
return session_conf
end

Expand Down Expand Up @@ -295,7 +309,33 @@ local schema = {
description = "keepalive timeout in milliseconds",
},
}
}
},
revocation = {
type = "string",
enum = {"redis"},
description = "Redis-backed revocation for cookie sessions.",
},
revocation_fail_mode = {
type = "string",
enum = {"open", "closed"},
Comment thread
Hockenba marked this conversation as resolved.
Resolved
description =
"When the revocation store is unreachable, open treats the "
.. "session as not revoked and closed rejects open/destroy.",
},
revocation_cache_ttl = {
type = "integer",
minimum = 0,
Comment thread
Hockenba marked this conversation as resolved.
Resolved
description =
"Worker-local TTL in seconds for successful revocation "
.. "lookups. 0 disables. Default is 5 in lua-resty-session.",
},
revocation_error_cache_ttl = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Hockenba Hockenba Sep 16, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Still feel free to take a peak at to make sure that I implemented what was requested :)

type = "integer",
minimum = 0,
description =
"Worker-local TTL in seconds for revocation store errors. "
.. "0 disables. Default is 5 in lua-resty-session.",
},
},
required = {"secret"},
["if"] = {
Expand All @@ -306,6 +346,19 @@ local schema = {
["then"] = {
required = {"redis"},
},
allOf = {
{
["if"] = {
required = {"revocation"},
},
["then"] = {
required = {"redis"},
properties = {
storage = { const = "cookie" },
},
},
},
},
additionalProperties = false,
},
realm = {
Expand Down Expand Up @@ -1467,6 +1520,17 @@ local function authenticate(plugin_conf, ctx)
return 401
end

-- fail-closed only: resty.session appends the store error, so match
-- the prefix. Reject without destroying the cookie.
if core.string.find(err, "unable to check session revocation")
or core.string.find(err, "unable to mark session revoked") then
if session then
session:close()
end
core.log.error("OIDC session revocation store unavailable: ", err)
return 503
end

-- Recoverable authorization-callback failures: a stale state
-- (replayed or pruned callback), or the ID provider redirecting
-- back with error=temporarily_unavailable, e.g. Keycloak after
Expand Down
28 changes: 27 additions & 1 deletion docs/en/latest/plugins/openid-connect.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,11 @@ The `openid-connect` Plugin supports the integration with [OpenID Connect (OIDC)
| session.absolute_timeout | integer | False | | | Absolute session lifetime in seconds. Forwarded to lua-resty-session as `absolute_timeout`. |
| session.cookie.lifetime | integer | False | | | Deprecated. Mapped to `session.absolute_timeout` at runtime when `absolute_timeout` is not set. Use `session.absolute_timeout` instead. |
| session.storage | string | False | cookie | ["cookie", "redis"] | Session storage method. |
| session.redis | object | False | | | Redis configuration when `storage` is `redis`. |
| session.revocation | string | False | | ["redis"] | Session revocation backend. Set to `redis` to enable revocation for cookie sessions. Requires `session.redis` and cannot be used when `session.storage` is `redis`. |
| session.revocation_fail_mode | string | False | open | ["open", "closed"] | When the revocation store is unreachable, `open` treats the session as not revoked, while `closed` rejects session open and destroy operations. |
| session.revocation_cache_ttl | integer | False | | | Worker-local TTL in seconds for successful revocation lookups. `0` disables. lua-resty-session defaults to `5`. |
| session.revocation_error_cache_ttl | integer | False | | | Worker-local TTL in seconds for revocation store errors. `0` disables. lua-resty-session defaults to `5`. Forced to `0` when `revocation_cache_ttl` is `0`. |
| session.redis | object | False | | | Redis connection. Required when `storage` is `redis`, or when `revocation` is `redis`. |
| session.redis.host | string | False | 127.0.0.1 | | Redis host. |
| session.redis.port | integer | False | 6379 | | Redis port. |
| session.redis.username | string | False | | | Redis username. |
Expand Down Expand Up @@ -357,6 +361,28 @@ spec:

See [Implement Authorization Code Grant](../tutorials/keycloak-oidc.md#implement-authorization-code-grant) for a complete example to use the `openid-connect` Plugin to integrate with Keycloak using the authorization code flow.

### Cookie Session Revocation

Cookie sessions are stored by the client and cannot normally be invalidated individually by APISIX. To enable server-side revocation, set `session.revocation` to `redis` and configure a Redis denylist:

```json
"session": {
"secret": "your-session-secret-min-16-chars",
"storage": "cookie",
"revocation": "redis",
"revocation_fail_mode": "closed",
"revocation_cache_ttl": 5,
"revocation_error_cache_ttl": 1,
"redis": {
"host": "127.0.0.1",
"port": 6379,
"prefix": "oidc:session:"
}
}
```

`revocation_fail_mode` defaults to `open`, which continues accepting sessions if the revocation store is unavailable. Use `closed` to reject session open and destroy operations instead. Opening a cookie session checks the revocation store, so each worker keeps a small cache of recent lookups to avoid one Redis call per request. `revocation_cache_ttl` is the worker-local TTL for successful lookups, and `revocation_error_cache_ttl` is the TTL for store errors. A logout takes effect immediately on the worker that handled it and on other workers once their cached entry expires, so `revocation_cache_ttl` is also the longest a revoked session can still be accepted elsewhere. Set `revocation_cache_ttl` to `0` to check Redis on every request; the error cache is then disabled as well. Omit `revocation` to leave cookie-session revocation disabled.

### Authorization Code Flow with PAR and DPoP

To use Pushed Authorization Requests (PAR), set `par.enabled` to `true`. If `par.endpoint` is not configured, the Plugin uses the PAR endpoint from the well-known discovery document.
Expand Down
28 changes: 27 additions & 1 deletion docs/zh/latest/plugins/openid-connect.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,11 @@ import TabItem from '@theme/TabItem';
| session.absolute_timeout | integer | 否 | | | 会话绝对生存时间(秒)。作为 `absolute_timeout` 透传到 lua-resty-session。 |
| session.cookie.lifetime | integer | 否 | | | 已弃用。当未设置 `session.absolute_timeout` 时,运行时会将该值映射到 `session.absolute_timeout`。请改用 `session.absolute_timeout`。 |
| session.storage | string | 否 | cookie | ["cookie", "redis"] | 会话存储方式。 |
| session.redis | object | 否 | | | `storage` 为 `redis` 时的 Redis 配置。 |
| session.revocation | string | 否 | | ["redis"] | 会话吊销后端。设置为 `redis` 可为 Cookie 会话启用吊销功能。必须配置 `session.redis`,且不能在 `session.storage` 为 `redis` 时使用。 |
| session.revocation_fail_mode | string | 否 | open | ["open", "closed"] | 当吊销存储不可用时,`open` 将会话视为未吊销,`closed` 则拒绝打开和销毁会话。 |
| session.revocation_cache_ttl | integer | 否 | | | 成功吊销查询的 worker 本地缓存 TTL(秒)。`0` 禁用。lua-resty-session 默认为 `5`。 |
| session.revocation_error_cache_ttl | integer | 否 | | | 吊销存储错误的 worker 本地缓存 TTL(秒)。`0` 禁用。lua-resty-session 默认为 `5`。当 `revocation_cache_ttl` 为 `0` 时强制为 `0`。 |
| session.redis | object | 否 | | | Redis 连接配置。当 `storage` 为 `redis`,或 `revocation` 为 `redis` 时必填。 |
| session.redis.host | string | 否 | 127.0.0.1 | | Redis 主机。 |
| session.redis.port | integer | 否 | 6379 | | Redis 端口。 |
| session.redis.username | string | 否 | | | Redis 用户名。 |
Expand Down Expand Up @@ -356,6 +360,28 @@ spec:

详见[实现授权码授权](../tutorials/keycloak-oidc.md#实现-authorization-code-grant),获取使用 `openid-connect` 插件与 Keycloak 集成并使用授权码流程的完整示例。

### Cookie 会话吊销

Cookie 会话存储在客户端,APISIX 通常无法单独将其失效。要启用服务端吊销,请将 `session.revocation` 设置为 `redis` 并配置 Redis 拒绝列表:

```json
"session": {
"secret": "your-session-secret-min-16-chars",
"storage": "cookie",
"revocation": "redis",
"revocation_fail_mode": "closed",
"revocation_cache_ttl": 5,
"revocation_error_cache_ttl": 1,
"redis": {
"host": "127.0.0.1",
"port": 6379,
"prefix": "oidc:session:"
}
}
```

`revocation_fail_mode` 默认为 `open`,即吊销存储不可用时继续接受会话。使用 `closed` 则会拒绝打开和销毁会话。打开 Cookie 会话时会查询吊销存储,因此每个 worker 会缓存最近的查询结果,避免每个请求都访问一次 Redis。`revocation_cache_ttl` 是成功查询的 worker 本地 TTL,`revocation_error_cache_ttl` 是存储错误的 TTL。登出在处理该请求的 worker 上立即生效,在其他 worker 上则在其缓存条目过期后生效,因此 `revocation_cache_ttl` 也是已吊销会话在其他 worker 上仍可能被接受的最长时间。将 `revocation_cache_ttl` 设为 `0` 则每个请求都查询 Redis,此时错误缓存也会被禁用。省略 `revocation` 即不启用 Cookie 会话吊销。

### 使用 PAR 和 DPoP 的授权码流程

如需使用 Pushed Authorization Requests (PAR),请将 `par.enabled` 设置为 `true`。如果未配置 `par.endpoint`,插件将使用 well-known 发现文档中的 PAR 端点。
Expand Down
Loading