Skip to content

feat(openid-connect): update session config to support lua-resty-session, fixes deprecated session.cookie.lifetime#13178

Merged
nic-6443 merged 11 commits into
apache:masterfrom
francescodedomenico:feat/oidc-session-cookie
Jun 10, 2026
Merged

feat(openid-connect): update session config to support lua-resty-session, fixes deprecated session.cookie.lifetime#13178
nic-6443 merged 11 commits into
apache:masterfrom
francescodedomenico:feat/oidc-session-cookie

Conversation

@francescodedomenico

@francescodedomenico francescodedomenico commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Description

APISIX bundles lua-resty-session 4.1.5, but the openid-connect plugin's session schema still exposed the legacy session.cookie.lifetime property from the 3.x API. Since lua-resty-openidc passes the session configuration as-is to resty.session.start(), only properties recognized by the 4.x library actually take effect — so cookie.lifetime was silently ignored at runtime.

This PR fixes session.cookie.lifetime by mapping it to lua-resty-session 4.x's absolute_timeout, so the value users set actually affects session expiry. The session schema is redesigned to expose lua-resty-session 4.x options flat, under their real names, directly at session.*, so the values pass straight through to resty.session.start() without any translation layer.

Why does it matter

With this update users have full control of the OIDC sticky session, allowing them to:

  • declare multiple OIDC plugins in the same APISIX configuration, with different client IDs and cookie properties
  • scope sticky sessions to selected paths via session.cookie_path
  • configure the full set of lua-resty-session 4.x cookie and timeout options (security flags, SameSite, idling/rolling/absolute timeouts) with explicit, validated keys

New schema design

Session options are flat under session, using the exact key names that lua-resty-session 4.x consumes — no aliasing, no remapping, no pass-through. The schema is closed (additionalProperties: false), so an unknown or misspelled key fails validation instead of being silently dropped — which is exactly the failure mode that caused cookie.lifetime to stop working in the first place.

Property Type Purpose
session.cookie_name string Session cookie name
session.cookie_path string Cookie path scope
session.cookie_domain string Cookie domain scope
session.cookie_secure boolean Set the Secure cookie attribute
session.cookie_http_only boolean Set the HttpOnly cookie attribute
session.cookie_same_site string (Strict / Lax / None / Default) SameSite cookie attribute
session.idling_timeout integer Session idling timeout (seconds)
session.rolling_timeout integer Session rolling timeout (seconds)
session.absolute_timeout integer Absolute session lifetime (seconds)
session.cookie.lifetime integer Deprecated. Backward-compatible alias for session.absolute_timeout

Example using the new flat keys:

{
  "session": {
    "secret": "jwcE5v3pM9VhqLxmxFOH9uZaLo8u7KQK",
    "cookie_name": "oidc_session",
    "cookie_path": "/app",
    "cookie_secure": true,
    "cookie_same_site": "Strict",
    "idling_timeout": 600,
    "absolute_timeout": 7200
  }
}

Since the keys match what resty.session.start() already consumes, the plugin simply forwards them — no alias map, no build_session_opts() translation table, no precedence rules.

Fix for session.cookie.lifetime

Prior to this PR, session.cookie.lifetime was accepted by the schema but silently ignored at runtime — lua-resty-session 4.x no longer recognizes the nested cookie.lifetime key. This PR keeps session.cookie.lifetime accepted (marked deprecated) and maps it to absolute_timeout at runtime when absolute_timeout is not set, so existing configurations actually take effect. A one-line deprecation warning is logged so users can migrate. If both session.absolute_timeout and session.cookie.lifetime are set, absolute_timeout wins — no warning, since the flat key is the documented path.

Backward compatibility

This change is purely additive and ships in APISIX 3.x:

  • session.cookie.lifetime stays accepted (deprecated) and now actually takes effect — that's the bug fix.
  • All new flat keys (cookie_name, cookie_secure, idling_timeout, …) are new optional properties.
  • Nothing is removed, no types change, no new required fields.

Existing configurations such as:

{ "session": { "secret": "...", "cookie": { "lifetime": 7200 } } }

continue to validate and now correctly affect session expiry.

Why explicit keys instead of an additionalProperties pass-through

lua-resty-session is a pinned dependency, not a fast-moving external API. Listing supported options explicitly:

  • Catches typos and unknown keys at validation time, instead of silently dropping them at runtime (exactly the failure mode that masked the cookie.lifetime bug).
  • Keeps the documented surface honest about what the bundled library version actually supports.
  • Is consistent with how APISIX schemas already handle stable-library config (e.g. the redis block in this very plugin, limit-count's redis schema).

Adding a new option in the future is a one-line schema addition.

Files changed

  • apisix/plugins/openid-connect.lua — flat session schema using lua-resty-session 4.x key names; additionalProperties: false on both session and the deprecated session.cookie; build_session_opts() reduced to a shallow copy plus the legacy cookie.lifetime → absolute_timeout mapping with deprecation warning
  • docs/en/latest/plugins/openid-connect.md — flat-key documentation with deprecation note for session.cookie.lifetime
  • docs/zh/latest/plugins/openid-connect.md — Chinese documentation updated to match
  • t/plugin/openid-connect10.t — 13 schema and unit tests covering flat-key validation, cookie.lifetime deprecation warning, absolute_timeout-wins precedence, unknown-key rejection (under both session and session.cookie), and invalid cookie_same_site rejection

Acknowledgements

@nic-6443 and @Baoyuantop for pre-review and planning.

Which issue(s) this PR fixes:

Fixes #13177

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change
  • I have updated the documentation to reflect this change
  • I have verified that this change is backward compatible (If not, please discuss on the APISIX mailing list first)

4.x

Replace the deprecated `session.cookie.lifetime` property with the full
set of lua-resty-session 4.x configuration options: cookie settings
(cookie_name, cookie_path, cookie_domain, cookie_same_site, etc.),
timeout controls (idling_timeout, rolling_timeout, absolute_timeout),
remember/persistent session support, and additional options like
audience, hash_storage_key, and store_metadata.

BREAKING CHANGE: `session.cookie.lifetime` has been removed. Use
`idling_timeout`, `rolling_timeout`, and `absolute_timeout` instead.
@francescodedomenico
francescodedomenico marked this pull request as ready for review April 7, 2026 21:42
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. enhancement New feature or request labels Apr 7, 2026
@Baoyuantop

Copy link
Copy Markdown
Contributor

Hi @francescodedomenico, thank you for your contribution, but we need to evaluate whether it’s necessary to expose all the underlying library’s configurations. I’ll discuss this with the other maintainers. I look forward to hearing others’ thoughts.

idling_timeout, updated plugin configuration in openid-connect.t to
reflect new config schema
@kovasaurus

Copy link
Copy Markdown

I would like to chip into this discussion.

Prior to lua-resty-session 4.x we could use the nginx variables to setup the various features from lua-resty-session which were not exposed directly through openid-connect plugin in Apisix. After 4.x version, due to dependency chain of openid-connect apisix plugin -> lua-resty-openidc -> lua-resty-session it is only possible to configure those features by configuration which is sent from openid-connect plugin.

Effectively, the limited schema capabilities are now blocking us from using the features that exist in lua-resty-session.
This PR from OP addresses the cookie configuration, but I have additional example.

lua-resty-session supports Redis with sentinel configuration -> https://github.com/bungle/lua-resty-session/blob/master/README.md#redis-sentinels-configuration

I have tried to play around with it for a bit, and I've managed to create custom docker image from apisix:3.16.0 with lua-resty-redis-connector installed, adjusted the schema for openid-connect to support sentinel properties and were able to successfully connect to Redis sentinel. The setup is still being tested but so far looks good.

@Baoyuantop

Copy link
Copy Markdown
Contributor

Hi @francescodedomenico, another issue with the current PR is that the session.cookie.lifetime configuration option has been removed outright, without providing a migration path or backward compatibility. This constitutes a breaking change. As for the additional configurations in lua-resty-session, I think we can make them available by setting additionalProperties instead of cramming them all into the current schema.

@francescodedomenico

francescodedomenico commented Apr 17, 2026

Copy link
Copy Markdown
Contributor Author

hello @Baoyuantop

...without providing a migration path or backward compatibility. This constitutes a breaking change.

I did add a migration note in the PR, the closest valid alternative for session.cookie.lifetime is the absolute_timeout parameter (gathered from the lifetime description of the 3.10 resty session).

I think it would be more maintainable if we continue to support 1:1 mapping rather than keeping a deprecated parameter and to map it internally.
Alternatively I can map the lifetime interally to the absolute_timeout parameter while still accepting the same parameter in additionalProperties, this should ease the migration.

However if you tell me where I am supposed to also add the migration notes I will add them in a docs: commit.

As for the additional configurations in lua-resty-session, I think we can make them available by setting additionalProperties instead of cramming them all into the current schema.

I do agree that many parameters could be added in an session.cookie.additionalProperties field and describe the field as being mapped against the resty session configuration.

However I would like to suggest to at least expose the:

  • cookie-name
  • cookie-path

These two are a game changer for the user, we can have multiple oidc sessions, avoid "session" cookie conflict and have specific sessions being forwarded only to specific cookie-path.

Let me know, thank you.

Edit: fixed my answers being inglobated in quotes.
Edit-2: proposed a strategy to not remove session.cookie.lifetime

@Baoyuantop

Copy link
Copy Markdown
Contributor
  1. Breaking change handling: session.cookie.lifetime was removed outright without a backward-compatible migration path. Please add an internal mapping (e.g., map cookie.lifetime to absolute_timeout) or at least keep the old field during a deprecation period.

  2. Schema design: Rather than hardcoding all ~30 lua-resty-session 4.x properties into the schema, consider explicitly defining only the essential ones (e.g., cookie_name, cookie_path) and allowing the rest via additionalProperties. Benefits:

    • No APISIX schema changes needed when lua-resty-session adds new config options
    • Reduced maintenance burden
    • Cleaner schema

Looking forward to the code update for further review.

object

Restore session.cookie as a nested object with explicit name, path, and
lifetime properties that map to lua-resty-session 4.x cookie_name,
cookie_path, and absolute_timeout. Allow additional
boolean/number/string
properties under session.cookie to be passed through to
lua-resty-session
as-is, avoiding schema churn when the upstream library adds new options.

Restores backward compatibility for the session.cookie.lifetime field
that was removed in the previous change.
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:XL This PR changes 500-999 lines, ignoring generated files. labels Apr 21, 2026
@francescodedomenico

Copy link
Copy Markdown
Contributor Author

hello @Baoyuantop
I like this approach, I pushed changes and updated both the openid-connect.md and the description of this PR to match the current state of the branch.

  • session.cookie.lifetime is mapped to absolute_timeout
  • cookie_name and cookie_path are mapped to session.cookie.name and session.cookie.path to respect the schema structure

thank you

@Baoyuantop

Copy link
Copy Markdown
Contributor

The updated approach aligns well with our earlier discussion — clean schema with explicit name/path/lifetime plus additionalProperties pass-through. Tests are comprehensive. Thank you for the iteration.

One concern about key conflict in build_session_opts: if a user sets both lifetime and absolute_timeout (or name/cookie_name, path/cookie_path) under session.cookie, the pairs() iteration order is non-deterministic, so the final value of opts.absolute_timeout depends on the internal Lua hash table traversal order.

Suggestion — add a conflict warning or precedence rule, e.g.:

local cookie = session_conf.cookie
if cookie then
    for k, v in pairs(cookie) do
        local mapped = cookie_key_map[k]
        local target_key = mapped or k
        if mapped and cookie[target_key] then
            core.log.warn("session.cookie: both '", k, "' and '", target_key,
                          "' are set; using '", k, "' (mapped to '", target_key, "')")
        end
        opts[target_key] = v
    end
end

Alternatively, document clearly that users should not set both a mapped alias and its corresponding flat key simultaneously.

@kovasaurus

Copy link
Copy Markdown

@Baoyuantop I would like to point out that this addresses only cookie properties in the session object.
However the lua-resty-session has matured enough to support additional storage options for session object.
Redis storage is only partially addressed in the the openid-connect plugin's schema.

Would it make sense to allow additional options to entire session object ( to support the rest of the storage options which are already exposed by lua-resty-session ) and existing session.redis to support redis cluster/sentinels?

All configuration options from lua-resty-session -> here

conflicts

When both an explicit alias (name/path/lifetime) and its underlying
lua-resty-session key (cookie_name/cookie_path/absolute_timeout) are set
under session.cookie, aliases now always take precedence regardless of
pairs() iteration order. A warning is logged on conflict.
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Apr 22, 2026
@francescodedomenico

francescodedomenico commented Apr 22, 2026

Copy link
Copy Markdown
Contributor Author

@kovasaurus while I do believe it would be a great addition to support other storage options I think this should be addressed in a whole different PR -> I'd like not to broad the scope of this one.
@Baoyuantop good observation, I made the the aliases explicitly declared in the schema to have precedence over the ones in additionalProperties, warning is logged when both are set so the user has evidence of what's happening.
Documentation has been updated and order of precedence explained in the openid markdown under the session.cookie. parameter.

I have noticed that the previous workflow had a couple of jobs failed but they didn't seem related to openid-connect.lua to me.

The previous merge of master into feat/oidc-session-cookie accepted
ours for the EN openid-connect.md attribute table, dropping the
re-port improvements from apache#13247 (a3c4e8c) while keeping only the
branch session.cookie.* additions. Restore master's table and
re-inject the session.cookie.{name,path,lifetime,<other>} rows and
the updated session.cookie description.

The ZH docs were merged correctly and are unchanged.
@mbsjed

mbsjed commented Jun 9, 2026

Copy link
Copy Markdown

Since these functionalities would also be quite important for us, i was wondering what the timeline looks like. Is there any plan in place or any indication of when a release can be expected?

@nic-6443

nic-6443 commented Jun 9, 2026

Copy link
Copy Markdown
Member

Thanks for picking this up — fixing cookie.lifetime is overdue. I'd like to suggest a slightly different shape before this lands, which I think ends up simpler than the alias approach here.

The core idea: expose the lua-resty-session 4.x options flat, under their real names, and enumerate them explicitly instead of using additionalProperties pass-through. Since the keys then match exactly what resty.session.start() already consumes, there's no alias map, no build_session_opts(), and no precedence rules — config passes straight through. Concretely:

session = {
    type = "object",
    properties = {
        secret = { type = "string", minLength = 16 },

        -- lua-resty-session 4.x cookie options, named exactly as the library expects
        cookie_name      = { type = "string" },
        cookie_path      = { type = "string" },
        cookie_domain    = { type = "string" },
        cookie_secure    = { type = "boolean" },
        cookie_http_only = { type = "boolean" },
        cookie_same_site = { type = "string", enum = {"Strict", "Lax", "None", "Default"} },

        -- session timeouts
        idling_timeout   = { type = "integer" },
        rolling_timeout  = { type = "integer" },
        absolute_timeout = { type = "integer" },

        -- deprecated: kept for backward compatibility, mapped to absolute_timeout at runtime
        cookie = {
            type = "object",
            properties = {
                lifetime = { type = "integer" },
            },
        },

        storage = { ... },   -- unchanged
        redis   = { ... },   -- unchanged
    },
    required = {"secret"}
}

The only thing that needs runtime translation is the legacy session.cookie.lifetime: keep accepting it (deprecated) and map it to absolute_timeout when absolute_timeout isn't set, so existing configs keep working — even though, as you noted, it's been silently ignored since the 4.x bump in #12862. Everything new is a direct lua-resty-session key, so it just flows through.

On dropping the additionalProperties pass-through in favor of an explicit list — a few reasons it's worth it here:

  • Silent pass-through is exactly how cookie.lifetime broke in the first place: a key the bundled lua-resty-session version doesn't recognize is accepted by the schema and then quietly dropped at runtime. An explicit allowlist turns that into a validation error instead of a silent no-op.
  • lua-resty-session is a pinned dependency, not a fast-moving external API. APISIX already enumerates stable-library config explicitly — see the redis block in this very plugin, or limit-count's redis schema. additionalProperties = true is reserved for volatile surfaces (AI provider options, protobuf decoder flags, OTel attributes), which this isn't. Kong's session and OIDC plugins do the same — every option is a named field.
  • Explicit fields are documentable and discoverable; with pass-through, users have to read the lua-resty-session source for the exact pinned version to know what's actually valid.

The tradeoff is that adding a new option later means a one-line schema addition, but that's cheap and keeps the surface honest about what the bundled library supports. Pick whichever cookie/timeout keys you think cover the common cases — the list above is a starting point, not exhaustive.

If you update the PR along these lines I'm happy to review. Does this direction work for you?

@nic-6443

nic-6443 commented Jun 9, 2026

Copy link
Copy Markdown
Member

@francescodedomenico thanks a lot for taking this on — it's an important fix, the broken cookie.lifetime has been silently biting users since the 4.x bump and the missing session options are a real gap. Would you be up for pushing the PR forward along the lines above? Happy to review quickly and help get it merged.

@mbsjed

mbsjed commented Jun 9, 2026

Copy link
Copy Markdown

@nic-6443 sounds great! We are currently preparing for a large scale rollout and have chosen APISIX. For security reasons we would very much like to use this feature (it has effectively become a requirement for us at this point :-)).

If there is any way i can help or contribute, i would be happy to do so.

@francescodedomenico

francescodedomenico commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Hello @nic-6443 and @mbsjed

I started working on this because I really needed it myself (especially being able to declare different OIDC clients and forward cookies only to specific subpaths).

My initial take was to directly expose the resty session configuration in order to take full advantage of the third party library, in my opinion apisix should not have an opinionated approach on how this library is used by mapping/remapping it's configuration.

That said, I changed the initial implementation due to the @Baoyuantop suggestion that also makes sense, deprecating completely the session.lifetime and exposing through the schema resty.session configuration would definitely be a breaking change, meaning that this won't reach Apisix until the 4.x release (if apisix uses semantic versioning).

In order to avoid a breaking change I suggest to expose the resty.session parameters like you suggested, keep the session.lifetime parameter and marking it as deprecated for the 3.x and to remove it completely when apisix goes 4.x.

This PR needs at least 3 reviewer also, before I move on I would love to reach some kind of consensus, I am not sure who else can/should express what kind of approach we want to follow.

@nic-6443

nic-6443 commented Jun 9, 2026

Copy link
Copy Markdown
Member

@francescodedomenico glad you're keen on this — I think we're closer than it sounds, but let me clear up one thing, because I suspect a version-number mix-up.

When I wrote "3.x" and "4.x" I meant the lua-resty-session library versions, not APISIX versions. #12862 bumped the bundled lua-resty-session from 3.10 to 4.1.5, and the flat cookie_* / *_timeout keys are simply that library's 4.x config surface. None of this is tied to an APISIX major version.

And the key point: the approach I proposed is not a breaking change — it's purely additive, so it can ship in APISIX 3.x. Nothing needs to wait for a hypothetical APISIX 4.0:

  • session.cookie.lifetime stays accepted (just marked deprecated) and is mapped to absolute_timeout. Existing configs keep validating and keep working.
  • The new flat keys (cookie_name, cookie_secure, idling_timeout, …) are new optional properties. Nothing is removed, no types change, no new required fields.

Concretely, both of these stay valid. Existing config — still works, and lifetime now actually takes effect (that's the bug fix):

{
  "session": {
    "secret": "jwcE5v3pM9VhqLxmxFOH9uZaLo8u7KQK",
    "cookie": { "lifetime": 7200 }
  }
}

New config — the flat lua-resty-session 4.x keys:

{
  "session": {
    "secret": "jwcE5v3pM9VhqLxmxFOH9uZaLo8u7KQK",
    "cookie_name": "oidc_session",
    "cookie_path": "/app",
    "cookie_same_site": "Strict",
    "cookie_secure": true,
    "absolute_timeout": 7200,
    "idling_timeout": 600
  }
}

On your point that APISIX shouldn't be opinionated about how the library is used — I actually agree, and that's exactly why I suggested the flat names: they're lua-resty-session's own config keys, with no remapping or aliasing in between. The only difference from a full additionalProperties pass-through is that we list the supported keys explicitly, so an unknown or misspelled key fails validation instead of being silently dropped — which is precisely the failure mode that let cookie.lifetime quietly stop working. You still get direct access to the library's config; you just get it validated.

Once we agree on this shape, I'll help bring in the other maintainers to review so the PR doesn't stall on the three-reviewer requirement. Does this clear up the breaking-change concern? If so, I think you're good to update the PR along these lines.

@nic-6443

nic-6443 commented Jun 9, 2026

Copy link
Copy Markdown
Member

@francescodedomenico one more thing — no pressure either way: if you have the bandwidth, this is your PR and I'm happy to just review and help it land. But if you're stretched thin, I'm glad to pick up the implementation and carry it forward with you so it doesn't stall — your work and authorship stay on it, I'd just help push it over the line. A few users here clearly need this, so let's make sure it lands one way or another. Just let me know which you'd prefer.

Restructure session config per @nic-6443 review on apache#13178: instead of
nested session.cookie.* aliases plus an additionalProperties
pass-through, expose lua-resty-session 4.x option names directly under
session (cookie_name, cookie_path, cookie_domain, cookie_secure,
cookie_http_only, cookie_same_site, idling_timeout, rolling_timeout,
absolute_timeout). Keys match what resty.session consumes, so values
pass through untouched.

Drop the alias map and the two-pass merge/precedence logic in
build_session_opts. The only remaining runtime translation is the
legacy session.cookie.lifetime: it stays accepted (deprecated), is
mapped to absolute_timeout when absolute_timeout is unset, and logs
a deprecation warning. When absolute_timeout is set, it wins.

session.cookie is restricted to { lifetime } with
additionalProperties=false; unknown keys (under cookie or directly
under session) are rejected by the schema instead of being silently
dropped — the failure mode that caused apache#13178 in the first place.

Purely additive vs master: existing { session: { cookie: { lifetime } } }
configs keep validating and now actually take effect.
@francescodedomenico

Copy link
Copy Markdown
Contributor Author

@nic-6443 I am on board with this, and luckily I had time to update the PR, like you I am interested in having this fixed for my personal and professional projects.

PR and description updated, the code diff from main is very little tbh , I hope review won't be difficult, let me know!

Comment thread apisix/plugins/openid-connect.lua Outdated
Comment thread apisix/plugins/openid-connect.lua Outdated
Comment thread t/plugin/openid-connect10.t Outdated
- Drop unused `local pairs = pairs` (luacheck warning surfaced by CI
  after build_session_opts no longer iterates the config table).
- Drop additionalProperties=false from session.cookie schema: rejecting
  previously-allowed extra fields under cookie has no benefit and is a
  needless breaking change for existing configs.
- TEST 5: assert cookie.lifetime stays in the returned table now that
  build_session_opts mutates session_conf in place instead of building
  a new opts table.
- Remove TEST 9 (unknown key under session.cookie rejection) — no
  longer enforced after dropping additionalProperties=false.
- Renumber subsequent tests 10–13 -> 9–12.
- TEST 11: fix regex to match the actual jsonschema error
  ("additional properties forbidden").
- TEST 12: drop leftover `session.cookie: both` line from the earlier
  alias implementation, which was never emitted by the current code.
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:XL This PR changes 500-999 lines, ignoring generated files. labels Jun 9, 2026
@francescodedomenico

francescodedomenico commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

thank you for the review @nic-6443 -> I have applied your recomendations and I did bootstrap devcontainers to run the tests locally, I'll let you to mark the open conversation as resolved if you think they are good

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: openid-connect plugin session.cookie.lifetime` has no effect (lua-resty-session 4.x incompatibility)

7 participants