Skip to content

fix(feishu-auth, dingtalk-auth): bind the authorization code to the session that started the login - #13806

Open
shreemaan-abhishek wants to merge 1 commit into
apache:masterfrom
shreemaan-abhishek:fix/oauth-state-binding
Open

fix(feishu-auth, dingtalk-auth): bind the authorization code to the session that started the login#13806
shreemaan-abhishek wants to merge 1 commit into
apache:masterfrom
shreemaan-abhishek:fix/oauth-state-binding

Conversation

@shreemaan-abhishek

Copy link
Copy Markdown
Contributor

Description

feishu-auth and dingtalk-auth implement the OAuth 2.0 authorization code flow, but neither carried a state parameter across it. The plugin emitted the login redirect with no state, and on the way back it took the code straight out of the query string and exchanged it, with nothing tying that code to the browser that started the flow. A code obtained elsewhere was therefore accepted on any session.

This PR closes that gap:

  • Before redirecting to redirect_uri, generate a random 16-byte state, store it in the session cookie, and append it to the redirect URL (?state= or &state= depending on whether the configured URL already has a query string).
  • When the code arrives in the query string, require the state to match the one bound to the session, and respond 401 {"message":"Invalid state"} when it does not. The state is dropped from the session once used.
  • When the code arrives in the configured header (X-Feishu-Code / X-DingTalk-Code), no state is required. Those requests come from non-browser clients, which cannot be driven cross-site.

This mirrors what authz-casdoor already does in this repository (apisix/plugins/authz-casdoor.lua:187-196 generates and stores, :131-144 validates).

Also in this PR: feishu-auth called sess:delete(...) on the token refresh path. lua-resty-session 4.x has no delete method, so that path raised attempt to call method 'delete' (a nil value) and returned 500. Replaced with sess:set(key, nil), which is the supported way to drop a key. This is exercised by the new tests.

Behaviour change

Deployments that use the query-string code path now need state to survive the round trip: redirect_uri receives it as a query parameter, and the login page is expected to pass it through to the IdP so that it comes back on the callback. This is the standard OAuth handling of state. Two consequences worth calling out:

  • A user who was mid-flow across an upgrade (redirected before, returning after) has no state in their session and gets a 401. Retrying the flow succeeds.
  • Deployments that drive the plugin entirely through the code header are unaffected.

There is no new configuration option: an opt-out would just be a switch for turning the check back off.

No plugin schema fields were added or changed.

Which issue(s) this PR fixes:

Fixes #

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)

…ession that started the login

Both plugins emitted the login redirect and consumed the callback without a
state parameter, so a code from the query string was accepted regardless of
which session it arrived on.

Generate a random state before redirecting, carry it on the redirect URL, and
require it back on codes taken from the query string. Codes taken from the
configured header are exempt, since those requests come from non-browser
clients.

Also replaces sess:delete(), which does not exist in lua-resty-session 4.x and
crashed the feishu-auth token refresh path.
@dosubot dosubot Bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant