Skip to content

feat(auth): transparent JWT exchange#87

Merged
zfarrell merged 10 commits into
mainfrom
feat/transparent-jwt-exchange
Jun 1, 2026
Merged

feat(auth): transparent JWT exchange#87
zfarrell merged 10 commits into
mainfrom
feat/transparent-jwt-exchange

Conversation

@zfarrell

@zfarrell zfarrell commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

The SDK now transparently exchanges an API token for a short-lived JWT (via POST {host}/v1/auth/jwt) and sends the JWT on every request, with zero user-code change. Server-side enablers (Gateway route + OAuth client) are tracked separately in the monopoly repo.

Comment thread hotdata/_auth.py
Comment on lines +93 to +100
pool_args = {
"cert_reqs": cert_reqs,
"ca_certs": configuration.ssl_ca_cert,
"cert_file": configuration.cert_file,
"key_file": configuration.key_file,
"ca_cert_data": configuration.ca_cert_data,
}
# Mirror rest.py's hostname/SNI handling so the exchange call does not

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: _pool_from_config mirrors rest.py's TLS/proxy handling but silently drops configuration.socket_options (rest.py:99-100 forwards it as a pool_args["socket_options"] kwarg). The inline comment below justifies omitting retries/maxsize for the "fail fast, single request" exchange, but doesn't address socket_options. A user who configures socket-level options (e.g. TCP keepalive) for all other SDK requests will see the token-exchange pool diverge from that. Either forward it or add it to the "intentionally not mirrored" comment to make the choice explicit. (not blocking)

Comment thread hotdata/_auth.py Outdated
def _needs_exchange(self):
# Opt-out wins outright: any truthy HOTDATA_DISABLE_JWT_EXCHANGE means
# send the credential as-is, never touching the token endpoint.
if os.environ.get(_DISABLE_ENV):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: os.environ.get(_DISABLE_ENV) returns the raw string, so HOTDATA_DISABLE_JWT_EXCHANGE=0 and =false both disable the exchange (non-empty strings are truthy in Python). The module docstring documents this as "any truthy value", but users setting =0/=false to enable exchange will silently be opted out. Consider parsing only obviously-affirmative values (e.g. value.lower() in {"1","true","yes","on"}) — matches typical env-var conventions and removes the footgun. (not blocking)

Comment thread hotdata/configuration.py Outdated
Comment on lines +515 to +517
if self._token_manager is None:
return None
return self._token_manager.bearer_value()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit: the property reads self._token_manager twice. If another thread does cfg.api_key = None between the is None check and the .bearer_value() call, this raises AttributeError: 'NoneType' object has no attribute 'bearer_value'. The same risk you call out in auth_settings() (where you cache the value into BearerAuth_token) applies here too. Read once:

tm = self._token_manager
return None if tm is None else tm.bearer_value()

(not blocking) (not blocking)

claude[bot]
claude Bot previously approved these changes Jun 1, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only non-blocking nits inline. Code is well-structured, thread-safe, and the test coverage (single-flight, refresh/re-mint fallback, deepcopy round-trip, eyJ pass-through, malformed-body, opt-out, lazy host read) is thorough.

@zfarrell

zfarrell commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Addressed all three inline nits in e73d4f6 + 9cf117b:

  • _pool_from_config now forwards socket_options too (matches rest.py).
  • Opt-out env var only treats affirmative values (1/true/yes/on) as disable, so =0/=false no longer silently opt out.
  • api_key getter reads _token_manager once to avoid the same concurrent-reset race fixed in auth_settings().

Added tests for the first two; full suite 41 passed.

@zfarrell zfarrell merged commit 4e05b3b into main Jun 1, 2026
3 checks passed
@zfarrell zfarrell deleted the feat/transparent-jwt-exchange branch June 1, 2026 18:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant