feat(auth): make bearer-token resolution pluggable via AuthBackend protocol - #732
Closed
xiejava1018 wants to merge 1 commit into
Closed
feat(auth): make bearer-token resolution pluggable via AuthBackend protocol#732xiejava1018 wants to merge 1 commit into
xiejava1018 wants to merge 1 commit into
Conversation
…otocol Today, non-browser requests to Flocks authenticate via a single shared API token stored in .secret.json. Leaking it grants admin-equivalent access across all tenants — incompatible with multi-tenant service-to- service deployments (e.g. a facade issuing per-team JWTs). Extend the existing AuthBackend protocol with two OPTIONAL methods: - supports_bearer_token() default False - authenticate_bearer_token(token) default None Backends that override these opt in to serving Bearer auth themselves. The follow-up server-side PR will consult supports_bearer_token() in apply_auth_for_request and route accordingly; until then this PR is a pure protocol extension with zero behavior change. Use case: a JWTAuthBackend (sample in a separate PR) decodes a short-lived per-team JWT, returns LocalUser with tenant_ids populated, and gives the facade real per-team isolation in Flocks without sharing the API token across tenants. Refs: - PoC reference implementation (not part of this PR): github.com/xiejava1018/flocks @ workshop/poc-c0 - Design: see PR description
xiejava1018
pushed a commit
to xiejava1018/flocks
that referenced
this pull request
Sep 3, 2026
uvicorn 全局异常处理器把 pyjwt 异常包成 500 '鉴权处理异常', 破坏客户端 重试语义。框架契约: get_user_by_session_id → None ⇒ 401 '登录已过期'。 真实服务联调验证(FLOCKS_AUTH=workshop_jwt, uvicorn flocks.server.app:app): - 伪造 Cookie JWT → 401 登录已过期(修复前 500) - 合法 team JWT → GET/POST /api/session 200(真实会话 ses_* 创建成功) - Bearer-only → 401(设计事实: 不走 backend, 印证上游 PR AgentFlocks#732 动机) - env-gated 挂载在真实启动链路生效(TeamJWTAuthBackend registered) 单测同步: forged/expired 两用例改断言 None; 30/30 全绿。 另修复测试 docstring 全角引号导致的语法错误。
Author
|
关闭此 PR:误提到上游 AgentFlocks/flocks。已改在 fork 仓 xiejava1018/flocks 内部评审合并(workshop_auth 插件属于 Workshop 私有集成, 不适合直接进上游)。 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Today, non-browser requests to Flocks authenticate via a single shared API
token stored in
.secret.json. Leaking that token grants admin-equivalentaccess across all tenants — which is incompatible with multi-tenant
service-to-service deployments (e.g. an upstream facade that wants to call
Flocks with per-team identities).
Proposal
Extend the existing
AuthBackendprotocol with two optional methods:Backends that override these opt in to serving Bearer auth themselves.
Default behavior is unchanged — backends that don't implement the new
methods keep falling through to the existing API-token check.
Why this shape
working with the shared API token, no env-var churn.
which is already selected at startup.
cookie + bearer auth symmetrically.
pyjwtis already a transitive dependency; thecontract is just "give me a token, I give you a user".
Use case: multi-tenant facade
A facade issues a short-lived JWT per
(user, team)request:The Flocks deployment installs a backend (e.g.
JWTAuthBackend) thatimplements
authenticate_bearer_token()to decode + verify the JWT (JWKSfetch with cache) and return a
LocalUserwhoseto_auth_user().tenant_idscarries the team scope. The facade then calls Flocks with
Authorization: Bearer <jwt>and gets real per-team isolation insideFlocks — no shared API token involved.
Rollout plan
apply_auth_for_requestconsultssupports_bearer_token()and routes accordingly. Cookie pathcompletely untouched.
JWTAuthBackendfor the use case above.Migration
None required. All backends continue to behave as today unless they
explicitly opt in.
PoC / reference
A working PoC that demonstrates this protocol extension in a multi-tenant
facade scenario lives on the
workshop/poc-c0branch ofgithub.com/xiejava1018/flocks. It includes:flocks/workshop_auth/— a JWT-based backend that implementsauthenticate_bearer_token()tests/poc_workshop_auth.py— end-to-end verification using real_apply_auth_for_requesttests/poc_tenant_isolation.py— verifiestenant_idsreaches Flocks'policy layer (
flocks/contracts/access)The PoC is intentionally not part of this PR — it depends on
Workshop facade specifics that don't belong in upstream Flocks. Happy to
extract a generic
JWTAuthBackendas a follow-up if reviewers want.Files changed
flocks/auth/backend.py— two optional methods added (34 lines)tests/test_auth_backend_bearer_protocol.py— defaults + opt-in testsOpen questions
AuthBackendor on a separateBearerAuthBackendmixin? Leaning towardAuthBackendfor simplicity —the cookie/bearer symmetry is intentional.
pattern" so third-party backends can extend
extension_contextwithoutforking? Out of scope here, but worth a separate discussion.