Skip to content

feat: Read JWT from query params - #556

Merged
lcian merged 10 commits into
mainfrom
feat/object-url-read-only-token
Jul 15, 2026
Merged

feat: Read JWT from query params#556
lcian merged 10 commits into
mainfrom
feat/object-url-read-only-token

Conversation

@lcian

@lcian lcian commented Jul 15, 2026

Copy link
Copy Markdown
Member

Summary

Extends the Python client's Session.object_url to optionally produce a self-contained, token-carrying read URL, and teaches the server to accept the auth token from a query parameter so such URLs work.

  • Python client (clients/python): object_url(key, read_only_token_validity=timedelta | None). When read_only_token_validity is provided, it mints an object.read-only JWT scoped to the session's usecase and scope (via the existing mint_token), valid for that duration, base64url-encodes it, and appends it as the os_auth query parameter. Requires a SecretKey; raises ValueError otherwise. Default behavior (no arg) is unchanged.
  • Server (objectstore-server): from_token now falls back to the os_auth query parameter when no x-os-auth/Authorization header token is present. The header takes precedence when both are present. Presigned URLs (os_sig) still take their own path.

Design notes

  • Query param name is os_auth, consistent with the existing os_-prefixed presign params (os_sig, os_kid, …).
  • The JWT is already pre-base64-encoded, and .-separated.
  • Semantic difference from presigned URLs: a read-only JWT authorizes reads on any object in the session's usecase+scope for its validity window (the res claim carries usecase+scope, not the object key), whereas a presigned URL binds the exact path+method. Documented in the docstring and README.
  • object_url's existing http://host:port URL building (scheme/IPv6 handling) is intentionally left unchanged — that's a separate fix.

🤖 Generated with Claude Code

…query param

`Session.object_url` now accepts an optional `read_only_token_validity`. When
provided, it mints an `object.read`-only JWT scoped to the session's usecase
and scope, valid for that duration, base64url-encodes it, and appends it to the
URL as the `os_auth` query parameter. The resulting URL is self-contained: the
recipient can fetch the object without supplying an auth header.

The server now reads the auth token from the base64url-encoded `os_auth` query
parameter in addition to the `x-os-auth`/`Authorization` headers. The header
takes precedence when both are present.

Unlike a pre-signed URL, which authorizes a single request on one object, a
read-only token authorizes reads on any object in the session's usecase and
scope for the duration of its validity.

Co-Authored-By: Claude <noreply@anthropic.com>
@lcian
lcian requested a review from a team as a code owner July 15, 2026 12:05
@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.51%. Comparing base (e254a12) to head (01b4114).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #556      +/-   ##
==========================================
+ Coverage   87.45%   87.51%   +0.05%     
==========================================
  Files          93       93              
  Lines       14760    14784      +24     
==========================================
+ Hits        12909    12938      +29     
+ Misses       1851     1846       -5     
Components Coverage Δ
Rust Backend 92.21% <100.00%> (+0.02%) ⬆️
Rust Client 79.89% <ø> (ø)
Python Client 90.20% <100.00%> (+0.82%) ⬆️

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@lcian
lcian marked this pull request as draft July 15, 2026 12:28
@lcian lcian changed the title feat(py-client): Embed read-only token in object_url; accept os_auth query param feat: Read JWT from query params Jul 15, 2026
Comment thread clients/python/src/objectstore_client/client.py Outdated
Comment thread clients/python/src/objectstore_client/client.py Outdated
Comment thread clients/python/tests/test_e2e.py Outdated
Comment thread clients/python/tests/test_smoke.py Outdated
Comment thread objectstore-server/src/extractors/service.rs Outdated
Comment thread objectstore-server/tests/query_auth.rs
lcian and others added 9 commits July 15, 2026 15:00
Address review feedback:
- Restore test_smoke.py (smoke coverage not needed here)
- Fix object_url to emit the `os_auth` query param matching the server
- Trim the README Object URLs section to the essentials
- Simplify query_auth.rs: build claims with serde_json instead of structs,
  drop the redundant garbage-base64 e2e case (covered by the token_from_query
  unit test)

Co-Authored-By: Claude <noreply@anthropic.com>
A JWT's compact serialization is already base64url, whose alphabet is URL-safe,
so the token needs no extra encoding to sit in a query string. Put the raw JWT
in the `os_auth` param directly on both client and server, removing the
encode/decode round-trip and the base64 dependency from the server crate.

`token_from_query` is now an infallible extractor returning a borrowed `&str`.
A malformed query token now surfaces as a 401 (JWT parse failure) rather than a
400, consistent with the header path.

Co-Authored-By: Claude <noreply@anthropic.com>
…atch

Per review: run the URL through a real parser and extract os_auth from the
parsed query string, rather than a naive substring check. This adds coverage
for structural breakage such as a duplicate `?` or wrong query encoding.

Co-Authored-By: Claude <noreply@anthropic.com>
Per review: the manual query split did not URL-decode the value, so a proxy
that re-encodes the query string could corrupt the token. Deserialize into a
minimal `AuthParams { os_auth }` struct via axum's `Query::try_from_uri`, which
runs the proper URL-decoding path.

A missing query string (e.g. a tokenless request) deserializes gracefully to
`os_auth: None`, so this stays a no-op for unauthenticated requests.

Co-Authored-By: Claude <noreply@anthropic.com>
@lcian
lcian marked this pull request as ready for review July 15, 2026 13:46
@lcian
lcian requested a review from jan-auer July 15, 2026 13:46
@lcian
lcian merged commit b74a53a into main Jul 15, 2026
26 checks passed
@lcian
lcian deleted the feat/object-url-read-only-token branch July 15, 2026 14:06
lcian added a commit that referenced this pull request Jul 16, 2026
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.

2 participants