wristband-python-jwt 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Apitopia, Inc. (dba Wristband)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,74 @@
1
+ Metadata-Version: 2.4
2
+ Name: wristband-python-jwt
3
+ Version: 0.1.0
4
+ Summary: Framework-agnostic Python SDK for validating JWT access tokens issued by Wristband.
5
+ Author-email: Wristband <support@wristband.dev>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://wristband.dev
8
+ Project-URL: Repository, https://github.com/wristband-dev/python-jwt
9
+ Project-URL: Documentation, https://docs.wristband.dev
10
+ Keywords: api,auth,authentication,authorization,fastapi,flask,django,jwt,multi-tenant,multi-tenancy,oauth,oidc,sdk,secure,security,sso,validation,wristband
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Intended Audience :: Developers
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: Security
21
+ Classifier: Development Status :: 4 - Beta
22
+ Classifier: Framework :: FastAPI
23
+ Classifier: Framework :: Flask
24
+ Classifier: Framework :: Django
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: cryptography<45.0.0,>=41.0.0
29
+ Requires-Dist: httpx>=0.24.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: setuptools>=61; extra == "dev"
32
+ Requires-Dist: pytest<9.0.0,>=8.2.0; extra == "dev"
33
+ Requires-Dist: pytest-cov<6.0.0,>=5.0.0; extra == "dev"
34
+ Requires-Dist: pytest-httpx>=0.21.0; extra == "dev"
35
+ Requires-Dist: mypy>=1.10.0; extra == "dev"
36
+ Requires-Dist: flake8<7.0.0,>=6.0.0; extra == "dev"
37
+ Requires-Dist: flake8-pyproject>=1.2.0; extra == "dev"
38
+ Requires-Dist: pip-audit>=2.0.0; extra == "dev"
39
+ Requires-Dist: bandit>=1.7.0; extra == "dev"
40
+ Requires-Dist: build>=0.10.0; extra == "dev"
41
+ Requires-Dist: twine>=4.0.0; extra == "dev"
42
+ Requires-Dist: black>=23.0.0; extra == "dev"
43
+ Requires-Dist: isort>=5.12.0; extra == "dev"
44
+ Dynamic: license-file
45
+
46
+ # Wristband Framework-Agnostic JWT Validation SDK for Python
47
+
48
+ Wristband provides enterprise-ready auth that is secure by default, truly multi-tenant, and ungated for small businesses.
49
+
50
+ - Website: [Wristband Website](https://wristband.dev)
51
+ - Documentation: [Wristband Docs](https://docs.wristband.dev/)
52
+
53
+ For detailed setup instructions and usage guidelines, visit the project's GitHub repository.
54
+
55
+ - [Python JWT SDK - GitHub](https://github.com/wristband-dev/python-jwt)
56
+
57
+
58
+ ## Details
59
+
60
+ This SDK provides secure JWT validation capabilities for applications using Wristband authentication. It is framework-agnostic and works with FastAPI, Flask, Django, and other Python web frameworks. The SDK follows OWASP security best practices and is supported for Python 3.9+. Key functionalities include:
61
+
62
+ - Extracting Bearer tokens from HTTP Authorization headers.
63
+ - Validating JWT signatures using Wristband's JWKS endpoint.
64
+ - Verifying token claims including issuer, expiration, and algorithm allowlisting to prevent common JWT vulnerabilities.
65
+ - Automatic JWKS key caching and rotation for optimal performance.
66
+ - Comprehensive error handling with detailed validation messages.
67
+
68
+ You can learn more about JWTs in Wristband in our documentation:
69
+
70
+ - [JWTs and Signing Keys](https://docs.wristband.dev/docs/json-web-tokens-jwts-and-signing-keys)
71
+
72
+ ## Questions
73
+
74
+ Reach out to the Wristband team at <support@wristband.dev> for any questions regarding this SDK.
@@ -0,0 +1,405 @@
1
+ <div align="center">
2
+ <a href="https://wristband.dev">
3
+ <picture>
4
+ <img src="https://assets.wristband.dev/images/email_branding_logo_v1.png" alt="Wristband" width="297" height="64">
5
+ </picture>
6
+ </a>
7
+ <p align="center">
8
+ Enterprise-ready auth that is secure by default, truly multi-tenant, and ungated for small businesses.
9
+ </p>
10
+ <p align="center">
11
+ <b>
12
+ <a href="https://wristband.dev">Website</a> •
13
+ <a href="https://docs.wristband.dev/">Documentation</a>
14
+ </b>
15
+ </p>
16
+ </div>
17
+
18
+ <br/>
19
+
20
+ ---
21
+
22
+ <br/>
23
+
24
+ # Wristband JWT Validation SDK for Python
25
+
26
+ This framework-agnostic Python SDK validates JWT access tokens issued by Wristband for user or machine authentication. It uses the Wristband JWKS endpoint to resolve signing keys and verify RS256 signatures. Validation includes issuer verification, lifetime checks, and signature validation using cached keys. Developers should use this to protect routes and ensure that only valid, Wristband-issued access tokens can access secured APIs.
27
+
28
+ You can learn more about JWTs in Wristband in our documentation:
29
+
30
+ - [JWTs and Signing Keys](https://docs.wristband.dev/docs/json-web-tokens-jwts-and-signing-keys)
31
+
32
+ <br/>
33
+
34
+ ## Requirements
35
+
36
+ This SDK is designed to work with Python 3.9+ and any Python framework (FastAPI, Django, Flask, etc.). It uses minimal dependencies for maximum compatibility and security.
37
+
38
+ <br/>
39
+
40
+ ## 1) Installation
41
+
42
+ ```bash
43
+ pip install wristband-python-jwt
44
+ ```
45
+
46
+ or
47
+
48
+ ```bash
49
+ poetry add wristband-python-jwt
50
+ ```
51
+
52
+ or
53
+
54
+ ```bash
55
+ pipenv install wristband-python-jwt
56
+ ```
57
+
58
+ You should see the dependency added to your `requirements.txt` file:
59
+
60
+ ```txt
61
+ wristband-python-jwt==0.1.0
62
+ ```
63
+
64
+ Or in your `pyproject.toml`:
65
+
66
+ ```txt
67
+ dependencies = [
68
+ "wristband-python-jwt==0.1.0",
69
+ # ...other dependencies...
70
+ ]
71
+ ```
72
+
73
+ Or in your `Pipfile`:
74
+ ```txt
75
+ [packages]
76
+ wristband-python-jwt = "==0.1.0"
77
+ ```
78
+
79
+ <br/>
80
+
81
+ ## 2) Wristband Configuration
82
+
83
+ First, you'll need to make sure you have an Application in your Wristband Dashboard account. If you haven't done so yet, refer to our docs on [Creating an Application](https://docs.wristband.dev/docs/quick-start-create-a-wristband-application).
84
+
85
+ **Make sure to copy the Application Vanity Domain for next steps, which can be found in "Application Settings" for your Wristband Application.**
86
+
87
+ <br/>
88
+
89
+ ## 3) SDK Configuration
90
+
91
+ First, create an instance of `WristbandJwtValidator` in your server's directory structure in any location of your choice (i.e.: `src/wristband.py`). Then, you can export this instance and use it across your project. When creating an instance, you provide all necessary configurations for your application to correlate with how you've set it up in Wristband.
92
+
93
+ ```python
94
+ # src/wristband.py
95
+ from wristband.python_jwt import create_wristband_jwt_validator, WristbandJwtValidatorConfig
96
+
97
+ wristband_jwt_validator = create_wristband_jwt_validator(
98
+ WristbandJwtValidatorConfig(
99
+ wristband_application_vanity_domain='auth.yourapp.io'
100
+ )
101
+ )
102
+ ```
103
+
104
+ <br/>
105
+
106
+ ## 4) Extract and Validate JWT Tokens
107
+
108
+ The SDK provides methods to extract Bearer tokens from Authorization headers and validate them. Here are examples for a few frameworks:
109
+
110
+ ### FastAPI
111
+
112
+ ```python
113
+ # jwt_auth_middleware.py
114
+ from fastapi import Request, Response, status
115
+ from starlette.middleware.base import BaseHTTPMiddleware
116
+ from .wristband import wristband_jwt_validator
117
+
118
+ class JwtAuthMiddleware(BaseHTTPMiddleware):
119
+ async def dispatch(self, request: Request, call_next):
120
+ # Adjust paths as needed
121
+ if not request.url.path.startswith('/api/protected/'):
122
+ return await call_next(request)
123
+
124
+ try:
125
+ auth_header = request.headers.get("authorization")
126
+ token = wristband_jwt_validator.extract_bearer_token(auth_header)
127
+ result = wristband_jwt_validator.validate(token)
128
+
129
+ if not result.is_valid:
130
+ print(f"JWT validation middleware error: {result.error_message}")
131
+ return Response(status_code=status.HTTP_401_UNAUTHORIZED)
132
+
133
+ return await call_next(request)
134
+
135
+ except Exception as error:
136
+ print(f"JWT validation middleware error: {error}")
137
+ return Response(status_code=status.HTTP_401_UNAUTHORIZED)
138
+
139
+ # main.py
140
+ from fastapi import FastAPI, Request
141
+ from .middleware import JwtAuthMiddleware
142
+
143
+ app = FastAPI()
144
+
145
+ # Add JWT authentication middleware
146
+ app.add_middleware(JwtAuthMiddleware)
147
+
148
+ @app.get("/api/protected/data")
149
+ async def protected_data(request: Request):
150
+ return { "message": "Hello from protected API!" }
151
+ ```
152
+
153
+ <br/>
154
+
155
+ ### Django
156
+
157
+ ```python
158
+ # your_app/jwt_auth_middleware.py
159
+ from django.http import HttpResponse
160
+ from django.utils.deprecation import MiddlewareMixin
161
+ from .wristband import wristband_jwt_validator
162
+
163
+ class JwtAuthMiddleware(MiddlewareMixin):
164
+ def process_request(self, request):
165
+ # Adjust paths as needed
166
+ if not request.path.startswith('/api/protected/'):
167
+ return None
168
+
169
+ try:
170
+ auth_header = request.META.get('HTTP_AUTHORIZATION')
171
+ token = wristband_jwt_validator.extract_bearer_token(auth_header)
172
+ result = wristband_jwt_validator.validate(token)
173
+
174
+ if not result.is_valid:
175
+ print(f"JWT validation middleware error: {result.error_message}")
176
+ return HttpResponse(status=401)
177
+
178
+ except Exception as error:
179
+ print(f"JWT validation middleware error: {error}")
180
+ return HttpResponse(status=401)
181
+
182
+ # your_project/settings.py
183
+ MIDDLEWARE = [
184
+ # ...other middlewares...
185
+ 'your_app.middleware.JwtAuthMiddleware', # Add your JWT middleware
186
+ ]
187
+
188
+ # your_project/urls.py
189
+ from django.contrib import admin
190
+ from django.urls import path
191
+ from your_app import views
192
+
193
+ urlpatterns = [
194
+ # The JWT middleware will execute before the business logic occurs.
195
+ path('api/protected/data/', views.protected_view, name='protected_data'),
196
+ # ...other URLs...
197
+ ]
198
+ ```
199
+
200
+ <br/>
201
+
202
+ ### Flask
203
+
204
+ ```python
205
+ # jwt_auth_middleware.py
206
+ from flask import request, jsonify, g
207
+ from functools import wraps
208
+ from .wristband import wristband_jwt_validator
209
+
210
+ def jwt_auth_middleware():
211
+ # Adjust paths as needed
212
+ if not request.path.startswith('/api/protected/'):
213
+ return None
214
+
215
+ try:
216
+ auth_header = request.headers.get('Authorization')
217
+ token = wristband_jwt_validator.extract_bearer_token(auth_header)
218
+ result = wristband_jwt_validator.validate(token)
219
+
220
+ if not result.is_valid:
221
+ print(f"JWT validation middleware error: {result.error_message}")
222
+ return '', 401
223
+
224
+ except Exception as error:
225
+ print(f"JWT validation middleware error: {error}")
226
+ return '', 401
227
+
228
+ # app.py
229
+ from flask import Flask, jsonify
230
+ from .middleware import jwt_auth_middleware
231
+
232
+ app = Flask(__name__)
233
+
234
+ # Register JWT middleware to run before each request
235
+ app.before_request(jwt_auth_middleware)
236
+
237
+ @app.route('/api/protected/data', methods=['GET'])
238
+ def protected_data():
239
+ return jsonify({"message": "Hello from protected API!"})
240
+
241
+ if __name__ == '__main__':
242
+ app.run(debug=True)
243
+ ```
244
+
245
+ <br/>
246
+
247
+ ## JWKS Caching and Expiration
248
+
249
+ The SDK automatically retrieves and caches JSON Web Key Sets (JWKS) from your Wristband application's domain to validate incoming access tokens. By default, keys are cached in memory and reused across requests to avoid unnecessary network calls.
250
+
251
+ You can control how the SDK handles this caching behavior using two optional configuration values: `jwks_cache_max_size` and `jwks_cache_ttl`.
252
+
253
+ **Set a limit on how many keys to keep in memory:**
254
+ ```python
255
+ validator = create_wristband_jwt_validator(
256
+ WristbandJwtValidatorConfig(
257
+ wristband_application_vanity_domain='auth.yourapp.io',
258
+ jwks_cache_max_size=10 # Keep at most 10 keys in cache
259
+ )
260
+ )
261
+ ```
262
+
263
+ **Set a time-to-live duration for each key:**
264
+ ```python
265
+ validator = create_wristband_jwt_validator(
266
+ WristbandJwtValidatorConfig(
267
+ wristband_application_vanity_domain='auth.yourapp.io',
268
+ jwks_cache_ttl=2629746000 # Expire keys from cache after 1 month (in milliseconds)
269
+ )
270
+ )
271
+ ```
272
+
273
+ If `jwks_cache_ttl` is not set, cached keys remain available until evicted by the cache size limit.
274
+
275
+ <br>
276
+
277
+ ## SDK Configuration Options
278
+
279
+ | JWT Validation Option | Type | Required | Description |
280
+ | --------------------- | ---- | -------- | ----------- |
281
+ | jwks_cache_max_size | int | No | Maximum number of JWKs to cache in memory. When exceeded, the least recently used keys are evicted. Defaults to 20. |
282
+ | jwks_cache_ttl | int | No | Time-to-live for cached JWKs, in milliseconds. If not set, keys remain in cache until eviction by size limit. |
283
+ | wristband_application_vanity_domain | str | Yes | The Wristband vanity domain used to construct the JWKS endpoint URL for verifying tokens. Example: `myapp.wristband.dev`. |
284
+
285
+ <br/>
286
+
287
+ ## API Reference
288
+
289
+ ### `create_wristband_jwt_validator(config)`
290
+
291
+ This is a factory function that creates a configured JWT validator instance.
292
+
293
+ **Parameters:**
294
+ | Name | Type | Required | Description |
295
+ | ---- | ---- | -------- | ----------- |
296
+ | config | `WristbandJwtValidatorConfig` | Yes | Configuration options (see [SDK Configuration Options](#sdk-configuration-options)) |
297
+
298
+ **Returns:**
299
+ - The configured `WristbandJwtValidator` instance
300
+
301
+ **Example:**
302
+ ```python
303
+ validator = create_wristband_jwt_validator(
304
+ WristbandJwtValidatorConfig(
305
+ wristband_application_vanity_domain='myapp.wristband.dev',
306
+ jwks_cache_max_size=20,
307
+ jwks_cache_ttl=3600000
308
+ )
309
+ )
310
+ ```
311
+
312
+ <br/>
313
+
314
+ ### `extract_bearer_token(authorization_header)`
315
+
316
+ This is used to extract the raw Bearer token from an HTTP Authorization header. It can handle various input formats and validates the Bearer scheme according to [RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750).
317
+
318
+ The function will raise an error for the following cases:
319
+ - The Authorization header is missing
320
+ - The Authorization header is malformed
321
+ - The Authorization header contains multiple entries
322
+ - The Authorization header uses wrong scheme (i.e. not using `Bearer`)
323
+ - The Authorization header is missing the token value
324
+
325
+ **Parameters:**
326
+ | Name | Type | Required | Description |
327
+ | ---- | ---- | -------- | ----------- |
328
+ | authorization_header | str or list[str] | Yes | The Authorization header value(s) of the current request. |
329
+
330
+ **Returns:**
331
+ | Type | Description |
332
+ | ---- | ----------- |
333
+ | str | The extracted Bearer token |
334
+
335
+ **Valid usage examples:**
336
+ ```python
337
+ token1 = wristband_jwt_validator.extract_bearer_token('Bearer abc123')
338
+ token2 = wristband_jwt_validator.extract_bearer_token(['Bearer abc123'])
339
+ # From FastAPI request
340
+ token3 = wristband_jwt_validator.extract_bearer_token(request.headers.get('authorization'))
341
+ # From Django request
342
+ token4 = wristband_jwt_validator.extract_bearer_token(request.META.get('HTTP_AUTHORIZATION'))
343
+ ```
344
+
345
+ **Invalid cases that raise errors:**
346
+ ```python
347
+ wristband_jwt_validator.extract_bearer_token(['Bearer abc', 'Bearer xyz'])
348
+ wristband_jwt_validator.extract_bearer_token([])
349
+ wristband_jwt_validator.extract_bearer_token('Basic abc123')
350
+ wristband_jwt_validator.extract_bearer_token('Bearer ')
351
+ ```
352
+
353
+ ### `validate(token)`
354
+
355
+ Validates a JWT access token issued by Wristband. Performs comprehensive validation including format checking, signature verification, issuer validation, and expiration checks.
356
+
357
+ **Parameters:**
358
+ | Name | Type | Required | Description |
359
+ | ---- | ---- | -------- | ----------- |
360
+ | token | str | Yes | The Wristband JWT token to validate. |
361
+
362
+ **Returns:**
363
+ | Type | Description |
364
+ | ---- | ----------- |
365
+ | `JwtValidationResult` | Validation result object. |
366
+
367
+ JwtValidationResult attributes:
368
+ ```python
369
+ class JwtValidationResult:
370
+ is_valid: bool
371
+ payload: Optional[JWTPayload] # Present when is_valid is True
372
+ error_message: Optional[str] # Present when is_valid is False
373
+ ```
374
+
375
+ JWTPayload properties:
376
+ ```python
377
+ class JWTPayload:
378
+ iss: Optional[str] # Issuer
379
+ sub: Optional[str] # Subject (user ID)
380
+ aud: Optional[Union[str, List[str]]] # Audience
381
+ exp: Optional[int] # Expiration time (Unix timestamp)
382
+ nbf: Optional[int] # Not before (Unix timestamp)
383
+ iat: Optional[int] # Issued at (Unix timestamp)
384
+ jti: Optional[str] # JWT ID
385
+ # ... plus any additional Wristband custom claims...
386
+ ```
387
+
388
+ **Valid usage examples:**
389
+ ```python
390
+ result = validator.validate(token)
391
+
392
+ if result.is_valid:
393
+ print('User ID:', result.payload.sub)
394
+ print('Expires at:', datetime.fromtimestamp(result.payload.exp))
395
+ else:
396
+ print('Validation failed:', result.error_message)
397
+ ```
398
+
399
+ <br/>
400
+
401
+ ## Questions
402
+
403
+ Reach out to the Wristband team at <support@wristband.dev> for any questions regarding this SDK.
404
+
405
+ <br/>
@@ -0,0 +1,29 @@
1
+ # Wristband Framework-Agnostic JWT Validation SDK for Python
2
+
3
+ Wristband provides enterprise-ready auth that is secure by default, truly multi-tenant, and ungated for small businesses.
4
+
5
+ - Website: [Wristband Website](https://wristband.dev)
6
+ - Documentation: [Wristband Docs](https://docs.wristband.dev/)
7
+
8
+ For detailed setup instructions and usage guidelines, visit the project's GitHub repository.
9
+
10
+ - [Python JWT SDK - GitHub](https://github.com/wristband-dev/python-jwt)
11
+
12
+
13
+ ## Details
14
+
15
+ This SDK provides secure JWT validation capabilities for applications using Wristband authentication. It is framework-agnostic and works with FastAPI, Flask, Django, and other Python web frameworks. The SDK follows OWASP security best practices and is supported for Python 3.9+. Key functionalities include:
16
+
17
+ - Extracting Bearer tokens from HTTP Authorization headers.
18
+ - Validating JWT signatures using Wristband's JWKS endpoint.
19
+ - Verifying token claims including issuer, expiration, and algorithm allowlisting to prevent common JWT vulnerabilities.
20
+ - Automatic JWKS key caching and rotation for optimal performance.
21
+ - Comprehensive error handling with detailed validation messages.
22
+
23
+ You can learn more about JWTs in Wristband in our documentation:
24
+
25
+ - [JWTs and Signing Keys](https://docs.wristband.dev/docs/json-web-tokens-jwts-and-signing-keys)
26
+
27
+ ## Questions
28
+
29
+ Reach out to the Wristband team at <support@wristband.dev> for any questions regarding this SDK.
@@ -0,0 +1,115 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "wristband-python-jwt"
7
+ version = "0.1.0"
8
+ description = "Framework-agnostic Python SDK for validating JWT access tokens issued by Wristband."
9
+ readme = "README_PYPI.md"
10
+ license = "MIT"
11
+ authors = [
12
+ {name = "Wristband", email = "support@wristband.dev"},
13
+ ]
14
+ requires-python = ">=3.9"
15
+ dependencies = [
16
+ "cryptography>=41.0.0,<45.0.0",
17
+ "httpx>=0.24.0",
18
+ ]
19
+ keywords = [
20
+ "api",
21
+ "auth",
22
+ "authentication",
23
+ "authorization",
24
+ "fastapi",
25
+ "flask",
26
+ "django",
27
+ "jwt",
28
+ "multi-tenant",
29
+ "multi-tenancy",
30
+ "oauth",
31
+ "oidc",
32
+ "sdk",
33
+ "secure",
34
+ "security",
35
+ "sso",
36
+ "validation",
37
+ "wristband"
38
+ ]
39
+ classifiers = [
40
+ "Programming Language :: Python :: 3",
41
+ "Programming Language :: Python :: 3.9",
42
+ "Programming Language :: Python :: 3.10",
43
+ "Programming Language :: Python :: 3.11",
44
+ "Programming Language :: Python :: 3.12",
45
+ "Programming Language :: Python :: 3.13",
46
+ "Operating System :: OS Independent",
47
+ "Intended Audience :: Developers",
48
+ "Topic :: Software Development :: Libraries :: Python Modules",
49
+ "Topic :: Security",
50
+ "Development Status :: 4 - Beta",
51
+ "Framework :: FastAPI",
52
+ "Framework :: Flask",
53
+ "Framework :: Django",
54
+ ]
55
+
56
+ [project.optional-dependencies]
57
+ dev = [
58
+ "setuptools>=61",
59
+ "pytest>=8.2.0,<9.0.0",
60
+ "pytest-cov>=5.0.0,<6.0.0",
61
+ "pytest-httpx>=0.21.0",
62
+ "mypy>=1.10.0",
63
+ "flake8>=6.0.0,<7.0.0",
64
+ "flake8-pyproject>=1.2.0",
65
+ "pip-audit>=2.0.0",
66
+ "bandit>=1.7.0",
67
+ "build>=0.10.0",
68
+ "twine>=4.0.0",
69
+ "black>=23.0.0",
70
+ "isort>=5.12.0",
71
+ ]
72
+
73
+ [project.urls]
74
+ "Homepage" = "https://wristband.dev"
75
+ "Repository" = "https://github.com/wristband-dev/python-jwt"
76
+ "Documentation" = "https://docs.wristband.dev"
77
+
78
+ [tool.setuptools.packages.find]
79
+ where = ["src"]
80
+
81
+ [tool.setuptools.package-dir]
82
+ "" = "src"
83
+
84
+ [tool.setuptools.package-data]
85
+ "wristband" = ["py.typed"]
86
+
87
+ [tool.pytest.ini_options]
88
+ pythonpath = ["src"]
89
+ minversion = "8.0"
90
+ addopts = "-ra -q --strict-markers --strict-config"
91
+ testpaths = ["tests"]
92
+
93
+ [tool.flake8]
94
+ max-line-length = 120
95
+ extend-ignore = ["E203", "W503"]
96
+
97
+ [tool.mypy]
98
+ python_version = "3.9"
99
+ strict = true
100
+ warn_return_any = true
101
+ warn_unused_configs = true
102
+ disallow_untyped_defs = true
103
+ exclude = ["tests/"]
104
+
105
+ [tool.black]
106
+ line-length = 120
107
+ target-version = ['py39']
108
+
109
+ [tool.isort]
110
+ profile = "black"
111
+ line_length = 120
112
+
113
+ [tool.coverage.run]
114
+ source = ["src"]
115
+ omit = ["tests/*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,46 @@
1
+ """
2
+ Wristband JWT Validation SDK for Python.
3
+
4
+ Framework-agnostic Python SDK for validating JWT access tokens issued by Wristband
5
+ for user or machine authentication. Uses the Wristband JWKS endpoint to resolve
6
+ signing keys and verify RS256 signatures.
7
+
8
+ Example:
9
+ ```python
10
+ from wristband.python_jwt import create_wristband_jwt_validator, WristbandJwtValidatorConfig
11
+
12
+ # Create validator instance (reuse across requests)
13
+ validator = create_wristband_jwt_validator(
14
+ WristbandJwtValidatorConfig(wristband_application_vanity_domain='myapp.wristband.dev')
15
+ )
16
+
17
+ # Extract and validate token
18
+ def verify_token(authorization_header):
19
+ try:
20
+ token = validator.extract_bearer_token(authorization_header)
21
+ result = await validator.validate(token)
22
+
23
+ if result.is_valid:
24
+ return result.payload
25
+ else:
26
+ raise ValueError(result.error_message)
27
+ except Exception as error:
28
+ raise ValueError(f"Authentication failed: {error}")
29
+ ```
30
+ """
31
+
32
+ from .models import (
33
+ JWTPayload,
34
+ JwtValidationResult,
35
+ WristbandJwtValidator,
36
+ WristbandJwtValidatorConfig,
37
+ )
38
+ from .validator import create_wristband_jwt_validator
39
+
40
+ __all__ = [
41
+ "WristbandJwtValidator",
42
+ "WristbandJwtValidatorConfig",
43
+ "JWTPayload",
44
+ "JwtValidationResult",
45
+ "create_wristband_jwt_validator",
46
+ ]