Skip to content

fix(connect): send OIDC requests through our own httpx client (#2220) - #2222

Open
dudanogueira wants to merge 1 commit into
weaviate:mainfrom
dudanogueira:fix/httpx2-authlib-session
Open

dudanogueira wants to merge 1 commit into
weaviate:mainfrom
dudanogueira:fix/httpx2-authlib-session

Conversation

@dudanogueira

Copy link
Copy Markdown
Contributor

Motivation

Fixes #2220. With OIDC auth (bearer token, client credentials, username/password), every request fails as soon as httpx2 is installed next to the client. Installing openai/langchain is enough to pull it in:

  • async: TypeError: unsupported operand type(s) for +: 'float' and 'Timeout'
  • sync: TypeError: 'Timeout' object cannot be interpreted as an integer, which then gets masked by an AssertionError in close()

The authlib session was the HTTP client for every request. Since Authlib 1.8, authlib.integrations.httpx_client builds its sessions on httpx2.AsyncClient/httpx2.Client whenever httpx2 is importable. We then hand that client an httpx.Timeout on each request. httpx2 doesn't recognise the foreign object and puts the whole thing into each slot ({'connect': Timeout(...), ...}), so the socket layer receives a Timeout where it expects a number. API-key and anonymous connections aren't affected because they use our own client.

CI didn't catch this because requirements-devel.txt pins authlib==1.6.12, while setup.cfg allows authlib>=1.7.1,<2.0.0.

Approach

The authlib session now only holds and refreshes the OIDC token. Every request goes through the same httpx client that API-key and anonymous connections use. Nothing that relied on the session's HTTP behaviour is lost:

  • We already set Authorization: Bearer <token> ourselves on every request (__get_latest_headers → get_current_bearer_token).
  • Authlib's inline "refresh if expired" lives in request()/stream(). We only ever called build_request() + send(), so it never ran. Our background refresher was already the only refresh mechanism, and it now operates on self._oauth_session.

This works with both httpx- and httpx2-based authlib, so it needs no dependency pins or upgrades. It also doesn't conflict with a later move to httpx2 (#2155).

Rejected alternatives:

  • Capping authlib<1.8: this only postpones the problem, and it blocks users who need newer authlib.
  • Passing library-neutral values to the session (tuple timeouts, catching both exception families, loosening isinstance checks): this touches many call sites and stays fragile. Any future httpx object we pass to the session would bring the bug back.

Key areas for review

  • v4.py OIDC branch of __process_oidc_response: the session goes into _oauth_session, then __make_clients builds the real client.
  • v4.py close(): both the client and the session are closed.
  • _OAUTH_HTTP_ERRORS: token-fetch failures from an httpx2-based session still become AuthenticationFailedError.

Risks and mitigations

  • Behaviour change (an improvement): OIDC requests now use the client built with _make_mounts, so the configured Proxies, pool limits and retries apply. Before, the session never received them. Users who relied on OIDC requests bypassing their configured proxy would see a difference. Token requests still don't use these settings; that's addressed in a follow-up PR.
  • Token freshness: unchanged. The background refresher was already the only refresh path, and it keeps running on the session.

Testing

  • New test_oidc_requests_use_own_http_client (sync and async, three credential types each) checks that requests go through a plain httpx client and that close() releases the session. It fails on main and passes here, whichever authlib version is installed, so CI's pinned 1.6.12 still catches a regression.
  • mock_tests/test_auth.py, test_timeouts.py and test_collection.py (62 tests) pass with authlib 1.6.12, and with authlib 1.9.1 + httpx2 2.16.0.
  • End to end against a local fake OIDC provider, with authlib 1.9.1 + httpx2 and with authlib 1.7.2: connect, an authenticated call, a read timeout mapped to WeaviateTimeoutError, and close() all work for all three credential types in sync and async. On main, all six fail with authlib 1.9.1 + httpx2. The background refresh keeps fetching tokens for both the refresh-token and client-credentials paths.
  • pyright 1.1.400 with httpx2 installed: the 4 errors on main in _send (the httpx/httpx2 type mix) are gone.

…te#2220)

With OIDC auth the authlib session was the HTTP client for every request.
Authlib >= 1.8 builds its sessions on httpx2 whenever httpx2 is installed
(e.g. pulled in by openai/langchain), so the httpx Timeout we pass on each
request was read as a single number and every call failed: async with
"unsupported operand type(s) for +: 'float' and 'Timeout'", sync with
"'Timeout' object cannot be interpreted as an integer".

The session now only holds and refreshes the token. Requests go through the
same httpx client API-key and anonymous connections use; we already set the
bearer header per request and refresh in the background, and authlib's
inline refresh lives in request()/stream(), which we never called. close()
closes the session too, and token-fetch failures from an httpx2-based
session raise AuthenticationFailedError like httpx ones.

As a side effect OIDC requests now honour the configured proxies, pool
limits and retries, which the session never received.

@orca-security-eu orca-security-eu 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.

Orca Security Scan Summary

Status Check Issues by priority
Passed Passed Infrastructure as Code high 0   medium 0   low 0   info 0 View in Orca
Passed Passed SAST high 0   medium 0   low 0   info 0 View in Orca
Passed Passed Secrets high 0   medium 0   low 0   info 0 View in Orca
Passed Passed Vulnerabilities high 0   medium 0   low 0   info 0 View in Orca

This branch has not been deployed

No deployments
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.

error when client connect

1 participant