Problem
Some registries mishandle a stale/expired cached bearer token so the client cannot recover:
- One returns
401 with no WWW-Authenticate challenge when a stale token is presented. A cold, credential-free request to the same URL does return a usable challenge.
- Another returns a
401 whose challenge realm points at a different host, which IRealmValidator (correctly) rejects. A cold request returns a same-host realm.
Today Client.SendCoreAsync hard-codes the challenge to token flow: an unknown/absent challenge falls through to return response1, and a disallowed/missing realm throws — neither is recoverable by a consumer, because both happen before any token-acquisition hook runs.
Proposal
Introduce IAuthChallengeHandler, letting the caller own the "401 → authorization" decision, with a default (DefaultAuthChallengeHandler) that preserves today's behavior exactly. The client keeps only the HTTP mechanics (cached-token attach, retry send, caching). Recovery for non-conformant registries lives in a consumer-supplied handler plus a shipped example — the core stays free of upstream-specific logic (unlike the previously-closed #400, which baked cold-retry into the core loop).
Contracts:
AuthChallengeContext — the failed exchange (original request, 401 response, parsed scheme/parameters, host, scopes, whether a cached token was attached, whether the request is replayable) plus capabilities that delegate to the client: a credential-free probe, realm validation, scope merging, cached-token lookup, and Basic/Bearer token fetch.
AuthChallengeResolution — scheme + token (+ cache scope key / cache flag). null means "give up; return the 401 unchanged."
Behavior-preserving: no existing test changes.
Is this direction acceptable? Implementation is ready and I am happy to open the PR.
Problem
Some registries mishandle a stale/expired cached bearer token so the client cannot recover:
401with noWWW-Authenticatechallenge when a stale token is presented. A cold, credential-free request to the same URL does return a usable challenge.401whose challenge realm points at a different host, whichIRealmValidator(correctly) rejects. A cold request returns a same-host realm.Today
Client.SendCoreAsynchard-codes the challenge to token flow: an unknown/absent challenge falls through toreturn response1, and a disallowed/missing realm throws — neither is recoverable by a consumer, because both happen before any token-acquisition hook runs.Proposal
Introduce
IAuthChallengeHandler, letting the caller own the "401 → authorization" decision, with a default (DefaultAuthChallengeHandler) that preserves today's behavior exactly. The client keeps only the HTTP mechanics (cached-token attach, retry send, caching). Recovery for non-conformant registries lives in a consumer-supplied handler plus a shipped example — the core stays free of upstream-specific logic (unlike the previously-closed #400, which baked cold-retry into the core loop).Contracts:
AuthChallengeContext— the failed exchange (original request, 401 response, parsed scheme/parameters, host, scopes, whether a cached token was attached, whether the request is replayable) plus capabilities that delegate to the client: a credential-free probe, realm validation, scope merging, cached-token lookup, and Basic/Bearer token fetch.AuthChallengeResolution— scheme + token (+ cache scope key / cache flag).nullmeans "give up; return the 401 unchanged."Behavior-preserving: no existing test changes.
Is this direction acceptable? Implementation is ready and I am happy to open the PR.