Repository navigation
Description
Current design
Authentication is modelled as an ordinary Policy occupying the single Stage.AUTH pillar, and the bearer policy folds two very different jobs into one object.
KeyCredentialPolicyandBasicAuthPolicyare pure stampers: apply a header on the same-origin path, otherwise forward unchanged (packages/dexpace-sdk-core/src/dexpace/sdk/core/http/auth/policies.py:72-101).KeyCredentialPolicy's own docstring describes it as "SansIO-shaped (no chain wrapping needed)" (line 40).BearerTokenPolicy.send(policies.py:161-193) does five things in one method: acquire/stamp the token, send, invalidate the cache on 401, dispatch to the challenge handler /on_challenge, and drive a manual retry-with-refresh that closes the rejected response and raisesClientAuthenticationErroron terminal failure.AsyncBearerTokenPolicy.send(policies.py:332-364) repeats the whole shape.- All four share the
Policybase andSTAGE = Stage.AUTH.AUTHis a pillar that admits at most one policy (pipeline/stage.py:53,_PILLARSatstage.py:72-82; enforced instaged_builder.py:193-201), anddefault_pipeline/default_async_pipelinetake a singleauth: Policy | Noneparameter (pipeline/defaults.py:41,91and106,128).
So a stateless one-line stamper and a stateful, response-driven retry engine sit behind the same abstraction and contend for the same single slot.
Trade-off / concern
Two distinct things get conflated:
-
Stamping vs. challenge handling. Putting cache invalidation, response lifecycle (
response.close()), challenge dispatch, and terminal-401/407 handling inside the same method that also stamps the credential concentrates the security-sensitive logic in a method that must simultaneously be correct about HTTP retry mechanics. The retry/refresh shape is duplicated across the sync and async bearer policies, and it is exactly where the trickier auth defects have landed (cross-origin 401 evicting the cache, terminal 401/407 being surfaced or leaked on retry-failure, challenge re-issue crossing origin). One overloaded method being the recurring site is a signal the surface is doing too much. -
Pillar-of-one. Because
AUTHadmits a single policy and the default builders expose oneauthslot, a service that legitimately needs both an API key header and a bearer token (a common gateway pattern) cannot compose two auth policies through the sanctioned API. They can hand-roll a composite, or abusePRE_AUTH/POST_AUTH(non-pillar) plusforce=True, but the framework offers no first-class support for stacking credential application.
Proposed direction
Separate the two concerns:
- Keep credential application as small SansIO-style stampers.
KeyCredentialPolicyandBasicAuthPolicyalready are; the bearer token's stamping half (acquire-from-cache, refresh-when-stale, stamp) is the same shape once the response-handling is removed. - Lift the 401/407 challenge-response retry loop into its own wrapping policy — an
AuthChallengePolicyanalogous toRetryPolicy/RedirectPolicy, sitting at a wrapping stage so it re-issues once and owns response lifecycle (close-rejected, terminalClientAuthenticationError) uniformly for any credential type. Bearer refresh-on-401 then becomes a concern the wrapping policy signals to the credential+cache, rather than logic baked into the stamping policy. - With application reduced to stateless stampers, the
AUTH-pillar-of-one constraint can relax to allow stampers to stack, giving the API-key-plus-bearer case real framework support.
Trade-offs of this direction: more moving parts and an extra wrapping stage; and the bearer refresh-on-401 currently benefits from being co-located with stamping (the stamper knows the scopes/cache key directly), so the split has to thread the cache/credential through both halves cleanly — likely via the credential + TokenCache rather than the policy holding the state. The win is that the security-critical retry/invalidation logic lives in exactly one place and is shared by every credential scheme instead of being re-implemented per policy and per sync/async twin.
I'd like a read on whether the extra stage is worth it, or whether the single overloaded AUTH policy is the shape we want to keep.
Activity