@@ -91,6 +91,113 @@ Pass to `make_wsgi_app(oauth_resource_metadata=...)` to enable OAuth discovery.
9191
9292Raises ` ValueError ` if ` resource ` is empty or ` authorization_servers ` is empty.
9393
94+ ## Bearer Token Authentication
95+
96+ For API keys, opaque tokens, or any non-JWT bearer token, use ` bearer_authenticate ` .
97+ No extra dependencies beyond ` vgi-rpc[http] ` .
98+
99+ ### bearer_authenticate()
100+
101+ Factory that creates a bearer-token ` authenticate ` callback with a custom ` validate ` function.
102+ Supports any validation logic: database lookups, introspection endpoints, expiry checks, etc.
103+
104+ ``` python
105+ from vgi_rpc.http import bearer_authenticate, make_wsgi_app
106+ from vgi_rpc import AuthContext, RpcServer
107+
108+ def validate (token : str ) -> AuthContext:
109+ # Look up token in database, call an introspection endpoint, etc.
110+ user = db.get_user_by_api_key(token)
111+ if user is None :
112+ raise ValueError (" Invalid API key" )
113+ return AuthContext(
114+ domain = " apikey" ,
115+ authenticated = True ,
116+ principal = user.name,
117+ claims = {" role" : user.role},
118+ )
119+
120+ auth = bearer_authenticate(validate = validate)
121+
122+ server = RpcServer(MyService, MyServiceImpl())
123+ app = make_wsgi_app(server, authenticate = auth)
124+ ```
125+
126+ | Parameter | Type | Description |
127+ | ---| ---| ---|
128+ | ` validate ` | ` Callable[[str], AuthContext] ` | Receives the raw token, returns ` AuthContext ` on success, raises ` ValueError ` on failure |
129+
130+ ### bearer_authenticate_static()
131+
132+ Convenience wrapper for a fixed set of known tokens. Useful for development,
133+ testing, or services with a small number of pre-shared API keys.
134+
135+ ``` python
136+ from vgi_rpc.http import bearer_authenticate_static, make_wsgi_app
137+ from vgi_rpc import AuthContext, RpcServer
138+
139+ tokens = {
140+ " key-abc123" : AuthContext(domain = " apikey" , authenticated = True , principal = " alice" ),
141+ " key-def456" : AuthContext(domain = " apikey" , authenticated = True , principal = " bob" ,
142+ claims = {" role" : " admin" }),
143+ }
144+
145+ auth = bearer_authenticate_static(tokens = tokens)
146+
147+ server = RpcServer(MyService, MyServiceImpl())
148+ app = make_wsgi_app(server, authenticate = auth)
149+ ```
150+
151+ | Parameter | Type | Description |
152+ | ---| ---| ---|
153+ | ` tokens ` | ` Mapping[str, AuthContext] ` | Maps bearer token strings to pre-built ` AuthContext ` values |
154+
155+ ## chain_authenticate()
156+
157+ Compose multiple ` authenticate ` callbacks into a single callback.
158+ Authenticators are tried in order — ` ValueError ` (bad credentials) falls through
159+ to the next; ` PermissionError ` or other exceptions propagate immediately.
160+
161+ This lets you accept ** both** JWT and API key tokens on the same server:
162+
163+ ``` python
164+ from vgi_rpc.http import (
165+ bearer_authenticate_static,
166+ chain_authenticate,
167+ jwt_authenticate,
168+ make_wsgi_app,
169+ )
170+ from vgi_rpc import AuthContext, RpcServer
171+
172+ # Accept JWTs from your identity provider
173+ jwt_auth = jwt_authenticate(
174+ issuer = " https://auth.example.com" ,
175+ audience = " https://api.example.com/vgi" ,
176+ )
177+
178+ # Also accept static API keys
179+ api_key_auth = bearer_authenticate_static(tokens = {
180+ " sk-service-account" : AuthContext(
181+ domain = " apikey" , authenticated = True , principal = " ci-bot" ,
182+ ),
183+ })
184+
185+ # Try JWT first, fall back to API key lookup
186+ auth = chain_authenticate(jwt_auth, api_key_auth)
187+
188+ server = RpcServer(MyService, MyServiceImpl())
189+ app = make_wsgi_app(server, authenticate = auth)
190+ ```
191+
192+ | Behaviour | Exception | Result |
193+ | ---| ---| ---|
194+ | Credentials accepted | * (none)* | Returns ` AuthContext ` , stops chain |
195+ | Bad / missing credentials | ` ValueError ` | Tries next authenticator |
196+ | Authenticated but forbidden | ` PermissionError ` | Propagates immediately (401) |
197+ | Bug in authenticator | Any other exception | Propagates immediately (500) |
198+
199+ Raises ` ValueError ` at construction time if called with no authenticators.
200+
94201## jwt_authenticate()
95202
96203Factory that creates a JWT-validating ` authenticate ` callback using Authlib.
0 commit comments