feat(*): Revocation store for stateless cookie sessions - #209
Conversation
Redis Token Revocation Store
Drop redundant open tests and post-open revocation mutation; exercise revocation via redis.mode and real Redis integration where possible. Co-authored-by: Cursor <cursoragent@cursor.com>
Reorder happy-path tests first, dedupe overlap, and group fail-mode coverage.
| | `revocation` | `nil` | Storage used for cookie session revocation records. Use `nil` or `false` to disable, a storage name such as `"shm"`, `"redis"`, `"mysql"`, or `"postgres"`, a custom storage module name, or a storage `table` with `set`/`get` methods. | | ||
| | `revocation_fail_mode` | `"open"` | Behavior when the revocation store is unreachable, use `"open"` (treat as not revoked) or `"closed"` (reject the session). | |
There was a problem hiding this comment.
Thinking aloud, should we move these inside cookie storage configuration, see:
https://github.com/bungle/lua-resty-session#cookie-storage-configuration
Thus here we should have a new
| Option | Default | Description |
|---|---|---|
cookie |
nil |
Configuration for cookie storage, e.g. { revocation_storage = "redis", revocation_fail_mode = "open" } (see below) |
Or do you think we may want to use revocation with non-stateless cookies too later, see my comment below about mass revocations?
There was a problem hiding this comment.
Thinking aloud, should we move these inside
cookiestorage configuration, see: https://github.com/bungle/lua-resty-session#cookie-storage-configurationThus here we should have a new
Option Default Description
cookienilConfiguration for cookie storage, e.g.{ revocation_storage = "redis", revocation_fail_mode = "open" }(see below)
Or do you think we may want to userevocationwith non-stateless cookies too later, see my comment below about mass revocations?
I think we should keep revocation and revocation_fail_mode at the top level rather than nesting them under a cookie storage config.
There isn’t much benefit to coupling them to cookie storage. Revocation is more of a session-lifecycle policy than cookie-backend configuration, and nesting it would make it harder later if we want the same mechanism for non-stateless cookies, headers, or other storages — especially with mass revocations in mind. Reversing that later would be awkward for compatibility.
So I’d leave the options top-level, with the current behavior that they only apply when using cookie storage. But it is up to you ultimately of course.
| On every `session:open`, the library checks whether the session identifier is | ||
| revoked. On `session:destroy`, the identifier is written to the selected |
There was a problem hiding this comment.
I am wondering that should we also support mass revocations. E.g. revoke all sessions that are created before this timestamp, revoke all sessions belonging to this subject created before this timestamp etc. Revoke all sessions created for this audience before this timestamp, etc. Not that you need to implement all that, but just that we don't paint us in a corner with this. Non-stateless version of this is store_metadata option that keeps track of sid, aud, subject (making it possible to selective delete sessions, e.g. all sessions by subject).
There was a problem hiding this comment.
I am wondering that should we also support
massrevocations. E.g. revoke all sessions that are created before this timestamp, revoke all sessions belonging to this subject created before this timestamp etc. Revoke all sessions created for this audience before this timestamp, etc. Not that you need to implement all that, but just that we don't paint us in a corner with this. Non-stateless version of this isstore_metadataoption that keeps track ofsid,aud,subject(making it possible to selective delete sessions, e.g. all sessions bysubject).
Good idea — I’d treat mass revocations (by time, subject, audience, etc.) as a possible future feature rather than something to design into this PR.
For cookie/stateless sessions, per-sid revocation is enough for now. Bulk filters are a separate and more complex than just a simple 1 or 0 concern, and would likely lean on something like store_metadata (or richer revocation records) when/if someone wants them.
As long as we keep revocation keyed by session id and don’t hard-code assumptions against richer filters later, we’re not painting ourselves into a corner. No need to over-engineer for that here and now when it could be done in the future, so we don't balloon the scope of this PR too big.
Continues #208 (source branch changed; see comment there).
Summary
Adds optional storage-backed session revocation for stateless (cookie) sessions.
session:open, check whether the session ID is in the denylist.session:destroy, write a lightweight sentinel with TTL matching remaining session lifetime (no session payload stored).Revocation is only available when
storageisnilor"cookie". Select a backend explicitly withrevocation = "dshm","file","memcached","mysql","postgres","redis", or"shm"(custom storage modules and pre-builtset/gettables are also supported). Backends use their normal configuration section. Leaverevocationunset or setrevocation = falseto disable.revocation_fail_modedefaults to"open"(store unreachable → treat as not revoked; destroy still clears the cookie)."closed"rejects open/destroy when the store is unavailable.Revocation applies to full
destroy/ single-audiencelogout. It does not apply tosession:saverotation or partial multi-audience logout.Consumer: apache/apisix#13651.
Test plan
make unit/busted spec/06-revocation-1_spec.lua spec/07-revocation-2_spec.luaspec/06-revocation-1_spec.luafile,shm,redis,memcachedrevocation=falsespec/07-revocation-2_spec.luamysql,postgres, Redis Sentinel/Cluster,dshmrevocation_fail_mode, invalid revocation type/value, invalid fail mode