Skip to content

Commit 2353782

Browse files
rustyconoverclaude
andcommitted
Update README and docs homepage with OAuth discovery details
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c8462e6 commit 2353782

2 files changed

Lines changed: 45 additions & 1 deletion

File tree

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Define RPC interfaces as Python `Protocol` classes. The framework derives Arrow
2727
- **Transport-agnostic** — in-process pipes, subprocess, Unix domain sockets, shared memory, or HTTP
2828
- **Automatic schema inference** — Python type annotations map to Arrow types
2929
- **Pluggable authentication**`AuthContext` + middleware for HTTP auth (JWT, API key, etc.)
30+
- **OAuth discovery** — RFC 9728 protected resource metadata + JWT authentication via Authlib
3031
- **Runtime introspection** — opt-in `__describe__` RPC method for dynamic service discovery
3132
- **CLI tool**`vgi-rpc describe` and `vgi-rpc call` for ad-hoc service interaction
3233
- **Shared memory transport** — zero-copy batch transfer between co-located processes
@@ -50,6 +51,7 @@ pip install vgi-rpc[gcs] # Google Cloud Storage backend
5051
pip install vgi-rpc[cli] # CLI tool (typer + httpx)
5152
pip install vgi-rpc[external] # External storage fetch (aiohttp + zstandard)
5253
pip install vgi-rpc[otel] # OpenTelemetry instrumentation
54+
pip install vgi-rpc[oauth] # JWT authentication (Authlib)
5355
```
5456

5557
Requires Python 3.13+.
@@ -1245,6 +1247,45 @@ class MyServiceImpl:
12451247

12461248
Over pipe transport, `ctx.auth` is always `AuthContext.anonymous()` (unauthenticated).
12471249

1250+
### OAuth Discovery (RFC 9728)
1251+
1252+
vgi-rpc supports [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728) OAuth 2.0 Protected Resource Metadata, allowing clients to automatically discover a server's authentication requirements.
1253+
1254+
**Server setup** — pass `OAuthResourceMetadata` to `make_wsgi_app` to serve `/.well-known/oauth-protected-resource` and include `WWW-Authenticate` headers on 401 responses:
1255+
1256+
```python
1257+
from vgi_rpc import RpcServer
1258+
from vgi_rpc.http import OAuthResourceMetadata, jwt_authenticate, make_wsgi_app
1259+
1260+
metadata = OAuthResourceMetadata(
1261+
resource="https://api.example.com/vgi",
1262+
authorization_servers=("https://auth.example.com",),
1263+
scopes_supported=("read", "write"),
1264+
)
1265+
1266+
auth = jwt_authenticate(
1267+
issuer="https://auth.example.com",
1268+
audience="https://api.example.com/vgi",
1269+
)
1270+
1271+
server = RpcServer(MyService, MyServiceImpl())
1272+
app = make_wsgi_app(server, authenticate=auth, oauth_resource_metadata=metadata)
1273+
```
1274+
1275+
**Client discovery** — fetch the metadata to learn which authorization server(s) to use:
1276+
1277+
```python
1278+
from vgi_rpc.http import http_oauth_metadata
1279+
1280+
meta = http_oauth_metadata("https://api.example.com")
1281+
if meta is not None:
1282+
print(meta.authorization_servers) # ("https://auth.example.com",)
1283+
```
1284+
1285+
Clients can also discover auth requirements from a 401 response's `WWW-Authenticate` header using `parse_resource_metadata_url()` and `fetch_oauth_metadata()`.
1286+
1287+
**`jwt_authenticate()`** creates a ready-to-use `authenticate` callback that validates Bearer JWTs against a JWKS endpoint (with automatic key refresh on unknown `kid`). If `jwks_uri` is not provided, it is discovered from the issuer's `/.well-known/openid-configuration`. Requires `pip install vgi-rpc[oauth]`.
1288+
12481289
### Transport metadata
12491290

12501291
`ctx.transport_metadata` provides transport-level information (e.g. `remote_addr`, `user_agent` for HTTP). This is a read-only mapping populated by the transport layer.
@@ -1329,6 +1370,7 @@ The [`examples/`](examples/) directory contains runnable scripts demonstrating k
13291370
| [`testing_pipe.py`](examples/testing_pipe.py) | Unit-testing with `serve_pipe()` (no network) |
13301371
| [`testing_http.py`](examples/testing_http.py) | Unit-testing the HTTP transport with `make_sync_client()` |
13311372
| [`auth.py`](examples/auth.py) | HTTP authentication with Bearer tokens and guarded methods |
1373+
| [`oauth_discovery.py`](examples/oauth_discovery.py) | RFC 9728 OAuth discovery with JWT authentication |
13321374
| [`introspection.py`](examples/introspection.py) | Runtime service introspection with `enable_describe` |
13331375
| [`shared_memory.py`](examples/shared_memory.py) | Zero-copy shared memory transport with `ShmPipeTransport` |
13341376

docs/index.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Define RPC interfaces as Python [`Protocol`](https://docs.python.org/3/library/t
2929
- **Transport-agnostic** — in-process pipes, subprocess, Unix domain sockets, shared memory, or [HTTP](api/http.md) — see [Transports](api/transports.md)
3030
- **Automatic schema inference** — Python type annotations map to [Arrow types](api/serialization.md#type-mappings)
3131
- **Pluggable authentication**[`AuthContext`](api/auth.md) + middleware for HTTP auth (JWT, API key, etc.)
32+
- **OAuth discovery**[RFC 9728](api/oauth.md) protected resource metadata + JWT authentication via Authlib
3233
- **Runtime introspection** — opt-in [`__describe__`](api/introspection.md) RPC method for dynamic service discovery
3334
- **CLI tool**[`vgi-rpc describe` and `vgi-rpc call`](api/cli.md) for ad-hoc service interaction
3435
- **Shared memory transport** — zero-copy batch transfer between co-located processes — see [Transports](api/transports.md#shared-memory)
@@ -100,6 +101,7 @@ pip install vgi-rpc[gcs] # Google Cloud Storage backend
100101
pip install vgi-rpc[cli] # CLI tool (typer + httpx)
101102
pip install vgi-rpc[external] # External storage fetch (aiohttp + zstandard)
102103
pip install vgi-rpc[otel] # OpenTelemetry instrumentation
104+
pip install vgi-rpc[oauth] # JWT authentication (Authlib)
103105
```
104106

105107
Requires Python 3.13+.
@@ -134,7 +136,7 @@ with serve_pipe(Calculator, CalculatorImpl()) as proxy:
134136
print(proxy.add(a=2.0, b=3.0)) # 5.0
135137
```
136138

137-
See the [Examples](examples.md) page for streaming, HTTP transport, authentication, and more.
139+
See the [Examples](examples.md) page for streaming, HTTP transport, authentication, OAuth discovery, and more.
138140

139141
## Limitations
140142

0 commit comments

Comments
 (0)