0
# JWKS Client
1
2
HTTP client for fetching and caching JSON Web Key Sets from remote endpoints. Provides automatic key refresh, flexible caching strategies, and seamless integration with JWT verification workflows for dynamic key management.
3
4
## Capabilities
5
6
### JWKS Client Initialization
7
8
Creates an HTTP client configured for fetching JWKS from remote endpoints with customizable caching and network options.
9
10
```python { .api }
11
class PyJWKClient:
12
def __init__(self, uri: str, cache_keys: bool = False,
13
max_cached_keys: int = 16, cache_jwk_set: bool = True,
14
lifespan: int = 300, headers: dict = None,
15
timeout: int = 30, ssl_context = None):
16
"""
17
Initialize JWKS client for fetching remote key sets.
18
19
Args:
20
uri (str): JWKS endpoint URL
21
cache_keys (bool): Enable individual key caching
22
max_cached_keys (int): Maximum keys to cache (when cache_keys=True)
23
cache_jwk_set (bool): Enable JWK Set caching
24
lifespan (int): Cache lifespan in seconds (default 300/5min)
25
headers (dict): Additional HTTP headers
26
timeout (int): Request timeout in seconds
27
ssl_context: SSL context for HTTPS requests
28
29
Raises:
30
PyJWKClientError: Invalid configuration
31
"""
32
```
33
34
Usage examples:
35
36
```python
37
import jwt
38
from jwt import PyJWKClient
39
40
# Basic JWKS client
41
jwks_client = PyJWKClient("https://example.com/.well-known/jwks.json")
42
43
# With custom caching
44
jwks_client = PyJWKClient(
45
"https://example.com/.well-known/jwks.json",
46
cache_keys=True, # Cache individual signing keys
47
max_cached_keys=50, # Increase cache size
48
lifespan=600 # 10 minute cache
49
)
50
51
# With custom headers and timeout
52
jwks_client = PyJWKClient(
53
"https://example.com/.well-known/jwks.json",
54
headers={"User-Agent": "MyApp/1.0"},
55
timeout=10 # 10 second timeout
56
)
57
58
# Disable JWK Set caching for always-fresh keys
59
jwks_client = PyJWKClient(
60
"https://example.com/.well-known/jwks.json",
61
cache_jwk_set=False
62
)
63
```
64
65
### Key Retrieval
66
67
Methods for fetching and accessing signing keys from remote JWKS endpoints.
68
69
```python { .api }
70
class PyJWKClient:
71
def get_signing_key(self, kid: str) -> PyJWK:
72
"""
73
Get signing key by key ID, with automatic refresh on cache miss.
74
75
Args:
76
kid (str): Key ID to retrieve
77
78
Returns:
79
PyJWK: Signing key matching the key ID
80
81
Raises:
82
PyJWKClientError: Key not found or network error
83
PyJWKClientConnectionError: Connection failed
84
"""
85
86
def get_signing_key_from_jwt(self, token: str) -> PyJWK:
87
"""
88
Extract key ID from JWT header and retrieve corresponding key.
89
90
Args:
91
token (str): JWT token containing 'kid' in header
92
93
Returns:
94
PyJWK: Signing key for the token
95
96
Raises:
97
PyJWKClientError: Missing kid or key not found
98
PyJWKClientConnectionError: Network error
99
"""
100
101
def get_signing_keys(self, refresh: bool = False) -> list:
102
"""
103
Get all signing keys from the JWKS endpoint.
104
105
Args:
106
refresh (bool): Force refresh from remote endpoint
107
108
Returns:
109
list: List of PyJWK signing keys (use='sig' or None)
110
111
Raises:
112
PyJWKClientError: No signing keys found
113
PyJWKClientConnectionError: Network error
114
"""
115
```
116
117
Usage examples:
118
119
```python
120
import jwt
121
from jwt import PyJWKClient
122
from jwt.exceptions import PyJWKClientError
123
124
jwks_client = PyJWKClient("https://example.com/.well-known/jwks.json")
125
126
# Get key by ID
127
try:
128
signing_key = jwks_client.get_signing_key("my-key-id")
129
print(f"Found key: {signing_key.algorithm_name}")
130
except PyJWKClientError as e:
131
print(f"Key retrieval failed: {e}")
132
133
# Get key from JWT token
134
token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Im15LWtleS1pZCJ9..."
135
try:
136
signing_key = jwks_client.get_signing_key_from_jwt(token)
137
payload = jwt.decode(token, signing_key.key, algorithms=[signing_key.algorithm_name])
138
print(f"Token verified: {payload}")
139
except PyJWKClientError as e:
140
print(f"Token verification failed: {e}")
141
142
# Get all signing keys
143
signing_keys = jwks_client.get_signing_keys()
144
for key in signing_keys:
145
print(f"Key {key.key_id}: {key.algorithm_name}")
146
147
# Force refresh from remote
148
fresh_keys = jwks_client.get_signing_keys(refresh=True)
149
```
150
151
### JWK Set Management
152
153
Methods for managing and accessing complete JWK Sets from remote endpoints.
154
155
```python { .api }
156
class PyJWKClient:
157
def get_jwk_set(self, refresh: bool = False) -> PyJWKSet:
158
"""
159
Get complete JWK Set from endpoint with caching.
160
161
Args:
162
refresh (bool): Force refresh from remote endpoint
163
164
Returns:
165
PyJWKSet: Complete key set
166
167
Raises:
168
PyJWKClientError: Invalid JWKS format
169
PyJWKClientConnectionError: Network error
170
"""
171
172
def fetch_data(self):
173
"""
174
Fetch raw JWKS data from remote endpoint.
175
176
Returns:
177
dict: Raw JWKS data
178
179
Raises:
180
PyJWKClientConnectionError: Network or timeout error
181
"""
182
183
@staticmethod
184
def match_kid(signing_keys: list, kid: str) -> PyJWK | None:
185
"""
186
Find signing key by key ID from a list of keys.
187
188
Args:
189
signing_keys (list): List of PyJWK signing keys
190
kid (str): Key ID to match
191
192
Returns:
193
PyJWK | None: Matching key or None if not found
194
"""
195
```
196
197
Usage examples:
198
199
```python
200
import jwt
201
from jwt import PyJWKClient
202
203
jwks_client = PyJWKClient("https://example.com/.well-known/jwks.json")
204
205
# Get cached JWK Set
206
jwk_set = jwks_client.get_jwk_set()
207
print(f"Loaded {len(jwk_set.keys)} keys")
208
209
# Force refresh for latest keys
210
fresh_jwk_set = jwks_client.get_jwk_set(refresh=True)
211
212
# Access individual keys
213
rsa_key = jwk_set['rsa-key-1']
214
ec_key = jwk_set['ec-key-1']
215
216
# Low-level: fetch raw data
217
raw_jwks = jwks_client.fetch_data()
218
print(f"Raw JWKS: {raw_jwks}")
219
```
220
221
### Complete JWT Verification Workflow
222
223
Common patterns for integrating JWKS client with JWT verification:
224
225
```python
226
import jwt
227
from jwt import PyJWKClient
228
from jwt.exceptions import PyJWKClientError, InvalidTokenError
229
230
def verify_jwt_with_jwks(token: str, jwks_url: str) -> dict:
231
"""
232
Verify JWT using remote JWKS endpoint.
233
"""
234
jwks_client = PyJWKClient(jwks_url)
235
236
try:
237
# Get signing key from token
238
signing_key = jwks_client.get_signing_key_from_jwt(token)
239
240
# Verify and decode token
241
payload = jwt.decode(
242
token,
243
signing_key.key,
244
algorithms=[signing_key.algorithm_name],
245
audience="my-app",
246
issuer="https://my-auth-server.com"
247
)
248
249
return payload
250
251
except PyJWKClientError as e:
252
raise ValueError(f"Key retrieval failed: {e}")
253
except InvalidTokenError as e:
254
raise ValueError(f"Token validation failed: {e}")
255
256
# Usage
257
try:
258
payload = verify_jwt_with_jwks(
259
token="eyJ...",
260
jwks_url="https://auth.example.com/.well-known/jwks.json"
261
)
262
print(f"User: {payload.get('sub')}")
263
except ValueError as e:
264
print(f"Verification failed: {e}")
265
```
266
267
### Caching Strategies
268
269
PyJWKClient provides two levels of caching:
270
271
#### JWK Set Caching (Default: Enabled)
272
273
Caches the entire JWKS response for the specified lifespan:
274
275
```python
276
# Default: 5-minute JWK Set cache
277
jwks_client = PyJWKClient(uri, cache_jwk_set=True, lifespan=300)
278
279
# Custom cache duration
280
jwks_client = PyJWKClient(uri, lifespan=1800) # 30 minutes
281
282
# Disable JWK Set caching
283
jwks_client = PyJWKClient(uri, cache_jwk_set=False)
284
```
285
286
#### Individual Key Caching (Default: Disabled)
287
288
Caches individual signing key lookups using LRU cache:
289
290
```python
291
# Enable signing key caching
292
jwks_client = PyJWKClient(uri, cache_keys=True, max_cached_keys=16)
293
294
# Larger key cache
295
jwks_client = PyJWKClient(uri, cache_keys=True, max_cached_keys=100)
296
297
# Both caching levels enabled
298
jwks_client = PyJWKClient(
299
uri,
300
cache_jwk_set=True, # Cache full JWKS response
301
lifespan=600, # 10 minute JWKS cache
302
cache_keys=True, # Also cache individual key lookups
303
max_cached_keys=50 # LRU cache for 50 keys
304
)
305
```
306
307
### Error Handling
308
309
JWKS client specific exceptions and error patterns:
310
311
```python
312
import jwt
313
from jwt.exceptions import (
314
PyJWKClientError,
315
PyJWKClientConnectionError,
316
PyJWKError
317
)
318
319
jwks_client = PyJWKClient("https://example.com/.well-known/jwks.json")
320
321
try:
322
signing_key = jwks_client.get_signing_key("key-id")
323
except PyJWKClientConnectionError as e:
324
# Network/timeout errors
325
print(f"Connection failed: {e}")
326
# Implement retry logic or fallback
327
except PyJWKClientError as e:
328
# Key not found, invalid JWKS format, etc.
329
print(f"JWKS error: {e}")
330
except Exception as e:
331
print(f"Unexpected error: {e}")
332
333
# Graceful degradation example
334
def get_key_with_fallback(client, kid, backup_key=None):
335
try:
336
return client.get_signing_key(kid)
337
except (PyJWKClientError, PyJWKClientConnectionError):
338
if backup_key:
339
return backup_key
340
raise ValueError("Primary and backup key retrieval failed")
341
```
342
343
### Advanced Configuration
344
345
SSL and network customization:
346
347
```python
348
import ssl
349
from jwt import PyJWKClient
350
351
# Custom SSL context
352
ssl_context = ssl.create_default_context()
353
ssl_context.check_hostname = False # For testing only!
354
355
jwks_client = PyJWKClient(
356
"https://internal-auth.company.com/.well-known/jwks.json",
357
headers={
358
"Authorization": "Bearer internal-token",
359
"User-Agent": "MyApp/2.0"
360
},
361
timeout=5,
362
ssl_context=ssl_context
363
)
364
365
# Corporate proxy or special routing
366
jwks_client = PyJWKClient(
367
"https://auth.example.com/.well-known/jwks.json",
368
headers={"X-Forwarded-For": "internal.company.com"}
369
)
370
```