flask-advanced-auth 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.
Files changed (39) hide show
  1. flask_advanced_auth-0.1.0/.gitignore +71 -0
  2. flask_advanced_auth-0.1.0/CHANGELOG.md +33 -0
  3. flask_advanced_auth-0.1.0/LICENSE +21 -0
  4. flask_advanced_auth-0.1.0/PKG-INFO +263 -0
  5. flask_advanced_auth-0.1.0/README.md +223 -0
  6. flask_advanced_auth-0.1.0/flask_advanced_auth/TokenProxy.py +69 -0
  7. flask_advanced_auth-0.1.0/flask_advanced_auth/__init__.py +73 -0
  8. flask_advanced_auth-0.1.0/flask_advanced_auth/authentication_classes.py +168 -0
  9. flask_advanced_auth-0.1.0/flask_advanced_auth/blacklist_token.py +101 -0
  10. flask_advanced_auth-0.1.0/flask_advanced_auth/database.py +108 -0
  11. flask_advanced_auth-0.1.0/flask_advanced_auth/decorators.py +126 -0
  12. flask_advanced_auth-0.1.0/flask_advanced_auth/exceptions.py +67 -0
  13. flask_advanced_auth-0.1.0/flask_advanced_auth/factory.py +276 -0
  14. flask_advanced_auth-0.1.0/flask_advanced_auth/helpers.py +84 -0
  15. flask_advanced_auth-0.1.0/flask_advanced_auth/import_handler.py +69 -0
  16. flask_advanced_auth-0.1.0/flask_advanced_auth/initialize.py +60 -0
  17. flask_advanced_auth-0.1.0/flask_advanced_auth/jwt_rules.py +254 -0
  18. flask_advanced_auth-0.1.0/flask_advanced_auth/model_managers.py +85 -0
  19. flask_advanced_auth-0.1.0/flask_advanced_auth/models.py +43 -0
  20. flask_advanced_auth-0.1.0/flask_advanced_auth/py.typed +1 -0
  21. flask_advanced_auth-0.1.0/flask_advanced_auth/validator.py +125 -0
  22. flask_advanced_auth-0.1.0/pyproject.toml +113 -0
  23. flask_advanced_auth-0.1.0/tests/__init__.py +2 -0
  24. flask_advanced_auth-0.1.0/tests/conftest.py +95 -0
  25. flask_advanced_auth-0.1.0/tests/integration/__init__.py +2 -0
  26. flask_advanced_auth-0.1.0/tests/integration/test_full_flow.py +122 -0
  27. flask_advanced_auth-0.1.0/tests/test_TokenProxy.py +70 -0
  28. flask_advanced_auth-0.1.0/tests/test_authentication_classes.py +148 -0
  29. flask_advanced_auth-0.1.0/tests/test_blacklist_token.py +93 -0
  30. flask_advanced_auth-0.1.0/tests/test_database.py +81 -0
  31. flask_advanced_auth-0.1.0/tests/test_decorators.py +93 -0
  32. flask_advanced_auth-0.1.0/tests/test_factory.py +105 -0
  33. flask_advanced_auth-0.1.0/tests/test_helpers.py +59 -0
  34. flask_advanced_auth-0.1.0/tests/test_import_handler.py +49 -0
  35. flask_advanced_auth-0.1.0/tests/test_initialize.py +51 -0
  36. flask_advanced_auth-0.1.0/tests/test_jwt_rules.py +143 -0
  37. flask_advanced_auth-0.1.0/tests/test_model_manager.py +56 -0
  38. flask_advanced_auth-0.1.0/tests/test_models.py +31 -0
  39. flask_advanced_auth-0.1.0/tests/test_validator.py +102 -0
@@ -0,0 +1,71 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Distribution / packaging
7
+ .Python
8
+ build/
9
+ develop-eggs/
10
+ dist/
11
+ downloads/
12
+ eggs/
13
+ .eggs/
14
+ lib/
15
+ lib64/
16
+ parts/
17
+ sdist/
18
+ var/
19
+ wheels/
20
+ share/python-wheels/
21
+ *.egg-info/
22
+ .installed.cfg
23
+ *.egg
24
+ MANIFEST
25
+
26
+ # Virtual environments
27
+ .env
28
+ .venv
29
+ env/
30
+ venv/
31
+ ENV/
32
+ env.bak/
33
+ venv.bak/
34
+
35
+ # Testing
36
+ .tox/
37
+ .nox/
38
+ .coverage
39
+ .coverage.*
40
+ .cache
41
+ nosetests.xml
42
+ coverage.xml
43
+ *.cover
44
+ *.py,cover
45
+ .hypothesis/
46
+ .pytest_cache/
47
+ cover/
48
+ htmlcov/
49
+
50
+ # Editors
51
+ .vscode/
52
+ .idea/
53
+ *.swp
54
+ *.swo
55
+ *~
56
+ .DS_Store
57
+
58
+ # mypy
59
+ .mypy_cache/
60
+ .dmypy.json
61
+ dmypy.json
62
+
63
+ # SQLite databases (demo/dev)
64
+ instance/
65
+ *.db
66
+
67
+ # Env files with secrets
68
+ backend/.env
69
+
70
+ # Distribution
71
+ dist/
@@ -0,0 +1,33 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+ - Multi-rule JWT configuration via `JWT_AUTHENTICATION_RULES` dict
12
+ - Singleton `JWTAuthenticationRules` for centralized rule management
13
+ - `TokenFactory` for generating and validating access and refresh tokens
14
+ - `@authenticate` decorator for protecting Flask routes
15
+ - `BlackListed` class for token revocation
16
+ - `Database` singleton for bridging Flask-SQLAlchemy
17
+ - `import_handler` utility for dynamic dotted-path imports
18
+ - `PyJWT` proxy wrapper for encode/decode operations
19
+ - Pydantic-backed `JWTRuleDetails` validator for rule configuration
20
+ - `initialize_jwt_authentication()` bootstrap entry point
21
+ - MIT License
22
+
23
+ ### Fixed
24
+ - `parse_token()` was a stub โ€” now correctly extracts token from `Authorization` header
25
+ - `get_rule(None)` was returning the rule name string instead of the rule dict
26
+ - `@authenticate` decorator had misplaced `return wrapper` causing it to never execute the view
27
+ - `table` / `table_path` field aliases were misspelled as `"tabel"` / `"tabel_path"` in the Pydantic validator
28
+ - `is_blacklisted()` was ignoring `rule_name`, always reading the default rule's blacklist setting
29
+ - Added `_reset()` class methods to `Database` and `JWTAuthenticationRules` singletons for test isolation
30
+
31
+ ## [0.1.0] - TBD
32
+
33
+ _Initial public release._
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Waheed
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,263 @@
1
+ Metadata-Version: 2.4
2
+ Name: flask-advanced-auth
3
+ Version: 0.1.0
4
+ Summary: Advanced JWT authentication for Flask with multi-rule support, token tracking, and blacklisting.
5
+ Project-URL: Homepage, https://github.com/Wehda23/Flask-Advanced-Authentication
6
+ Project-URL: Repository, https://github.com/Wehda23/Flask-Advanced-Authentication
7
+ Project-URL: Bug Tracker, https://github.com/Wehda23/Flask-Advanced-Authentication/issues
8
+ Author: Waheed
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: auth,authentication,blacklist,flask,flask-sqlalchemy,jwt,token
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Framework :: Flask
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Internet :: WWW/HTTP :: Session
22
+ Classifier: Topic :: Security
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: flask-sqlalchemy>=3.0.0
26
+ Requires-Dist: flask>=2.3.0
27
+ Requires-Dist: pydantic>=2.0.0
28
+ Requires-Dist: pyjwt>=2.8.0
29
+ Requires-Dist: python-dotenv>=1.0.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: black>=23.0.0; extra == 'dev'
32
+ Requires-Dist: isort>=5.12.0; extra == 'dev'
33
+ Requires-Dist: mypy>=1.5.0; extra == 'dev'
34
+ Requires-Dist: pre-commit>=3.4.0; extra == 'dev'
35
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
36
+ Requires-Dist: pytest-flask>=1.3.0; extra == 'dev'
37
+ Requires-Dist: pytest>=7.4.0; extra == 'dev'
38
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # flask-advanced-auth
42
+
43
+ [![PyPI version](https://img.shields.io/pypi/v/flask-advanced-auth.svg)](https://pypi.org/project/flask-advanced-auth/)
44
+ [![Python versions](https://img.shields.io/pypi/pyversions/flask-advanced-auth.svg)](https://pypi.org/project/flask-advanced-auth/)
45
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
46
+ [![Tests](https://github.com/Wehda23/Flask-Advanced-Authentication/actions/workflows/ci.yml/badge.svg)](https://github.com/Wehda23/Flask-Advanced-Authentication/actions)
47
+ [![Coverage](https://img.shields.io/badge/coverage-88%25-brightgreen.svg)](https://github.com/Wehda23/Flask-Advanced-Authentication)
48
+
49
+ **flask-advanced-auth** is a production-grade, highly configurable JWT authentication extension for Flask and Flask-SQLAlchemy. It provides multi-rule support (different token lifetimes, secrets, and models for users vs. admins), built-in database token tracking, automatic token revocation (blacklisting), and clean route decorators.
50
+
51
+ ---
52
+
53
+ ## โœจ Features
54
+
55
+ - ๐Ÿ”‘ **Multi-Rule Authentication**: Define distinct rules for different user tiers (e.g. `users`, `managers`, `api_clients`) with independent secrets, algorithms, and lifetimes.
56
+ - ๐Ÿ›ก๏ธ **Easy Route Protection**: Clean `@authenticate(auth_classes=[AuthenticateJWT])` decorator with customizable 401 response and model injection into `request.instance`.
57
+ - ๐Ÿ”„ **Access & Refresh Tokens**: Native support for short-lived access tokens and long-lived refresh tokens with convenient `refresh_access_token()` helper.
58
+ - ๐Ÿšซ **Token Revocation (Blacklisting)**: Built-in revocation mechanism with database persistence and `logout()` helper.
59
+ - ๐Ÿ“Š **Token Tracking**: Track issued tokens in the database to detect anomalies, enforce single-session limits, or audit activity.
60
+ - ๐Ÿ”Œ **Multiple Extraction Strategies**: Extract tokens from `Authorization: Bearer <token>` headers, HTTP cookies, or URL query parameters.
61
+ - โšก **Pydantic Validation**: Strict configuration schema validation powered by Pydantic V2 with flexible aliases.
62
+ - ๐Ÿงช **Thoroughly Tested**: 100% test pass rate with โ‰ฅ 88% test coverage.
63
+
64
+ ---
65
+
66
+ ## ๐Ÿ“ฆ Installation
67
+
68
+ ```bash
69
+ pip install flask-advanced-auth
70
+ ```
71
+
72
+ Or install with development dependencies:
73
+
74
+ ```bash
75
+ pip install flask-advanced-auth[dev]
76
+ ```
77
+
78
+ ---
79
+
80
+ ## ๐Ÿš€ Quick Start (5 Minutes)
81
+
82
+ ### 1. Configure Rules & Bootstrap
83
+
84
+ ```python
85
+ from datetime import timedelta
86
+ from flask import Flask, jsonify, request
87
+ from flask_sqlalchemy import SQLAlchemy
88
+ from flask_advanced_auth import (
89
+ initialize_jwt_authentication,
90
+ authenticate,
91
+ AuthenticateJWT,
92
+ TokenFactory,
93
+ refresh_access_token,
94
+ logout,
95
+ )
96
+
97
+ app = Flask(__name__)
98
+ app.config["SECRET_KEY"] = "your-app-secret"
99
+ app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db"
100
+ db = SQLAlchemy(app)
101
+
102
+ # Define your JWT rules
103
+ JWT_RULES = {
104
+ "default": {
105
+ "description": "Standard user access token",
106
+ "secret_key": "your-jwt-secret-key",
107
+ "algorithm": "HS256",
108
+ "access_token_lifetime": timedelta(minutes=15),
109
+ "refresh_token_lifetime": timedelta(days=7),
110
+ "token_header": "Bearer ",
111
+ }
112
+ }
113
+
114
+ # Initialize flask-advanced-auth
115
+ initialize_jwt_authentication(
116
+ database_path="app.db",
117
+ rules=JWT_RULES,
118
+ )
119
+ ```
120
+
121
+ ### 2. Issue Tokens on Login
122
+
123
+ ```python
124
+ @app.route("/api/login", methods=["POST"])
125
+ def login():
126
+ username = request.json.get("username")
127
+
128
+ factory = TokenFactory()
129
+ access_token = factory.generate_token(
130
+ rule_name="default",
131
+ extra_payload={"sub": username},
132
+ )
133
+ refresh_token = factory.generate_refresh_token(
134
+ rule_name="default",
135
+ extra_payload={"sub": username},
136
+ )
137
+
138
+ return jsonify({
139
+ "access_token": access_token,
140
+ "refresh_token": refresh_token,
141
+ })
142
+ ```
143
+
144
+ ### 3. Protect Routes
145
+
146
+ ```python
147
+ @app.route("/api/dashboard")
148
+ @authenticate(auth_classes=[AuthenticateJWT], rule_name="default")
149
+ def dashboard():
150
+ return jsonify({"message": "Welcome to your protected dashboard!"})
151
+ ```
152
+
153
+ ### 4. Refresh Tokens & Logout
154
+
155
+ ```python
156
+ @app.route("/api/refresh", methods=["POST"])
157
+ def refresh():
158
+ token = request.json.get("refresh_token")
159
+ new_access_token = refresh_access_token(token, rule_name="default")
160
+ return jsonify({"access_token": new_access_token})
161
+
162
+ @app.route("/api/logout", methods=["POST"])
163
+ def user_logout():
164
+ token = request.headers.get("Authorization", "").replace("Bearer ", "")
165
+ logout(token, rule_name="default")
166
+ return jsonify({"message": "Token revoked successfully."})
167
+ ```
168
+
169
+ ---
170
+
171
+ ## โš™๏ธ Configuration Reference
172
+
173
+ Rules are passed as a dictionary where each key represents a rule name (e.g. `"default"`, `"admin"`):
174
+
175
+ | Field | Type | Default | Description |
176
+ |---|---|---|---|
177
+ | `description` | `str` | *Required* | Description of what this token rule is used for. |
178
+ | `secret_key` | `str` | *Required* | Secret signing key for encoding and decoding tokens. |
179
+ | `algorithm` | `str` | *Required* | JWT signing algorithm (e.g. `'HS256'`, `'RS256'`). |
180
+ | `access_token_lifetime` | `timedelta` | *Required* | Expiry duration for access tokens. |
181
+ | `refresh_token_lifetime` | `timedelta` | `None` | Expiry duration for refresh tokens. |
182
+ | `token_header` | `str` | `'Bearer '` | Expected header prefix in `Authorization` header. |
183
+ | `table` | `bool` | `False` | Whether to link tokens to a database entity. |
184
+ | `table_path` | `str` | `None` | Dotted import path to model class (e.g. `'myapp.models.User'`). |
185
+ | `track_created` | `bool` | `False` | Persist issued tokens to the database. |
186
+ | `track_created_table_path` | `str` | `None` | Dotted import path to TrackModel class. |
187
+ | `track_created_allow_duplicates` | `bool` | `True` | Allow multiple active tokens for the same user. |
188
+ | `blacklisted` | `bool` | `False` | Enable token blacklist checks on verification. |
189
+ | `blacklisted_table_path` | `str` | `None` | Dotted import path to BlackListModel class. |
190
+
191
+ ---
192
+
193
+ ## ๐Ÿงฉ Authentication Strategies
194
+
195
+ You can choose how tokens are extracted by passing different authentication classes to `@authenticate`:
196
+
197
+ ```python
198
+ from flask_advanced_auth import (
199
+ authenticate,
200
+ HeaderAuthentication, # Reads Authorization: Bearer <token>
201
+ CookieAuthentication, # Reads token from HTTP cookies
202
+ QueryParamAuthentication, # Reads ?token=<token>
203
+ )
204
+
205
+ # 1. From Authorization header
206
+ @app.route("/api/v1/data")
207
+ @authenticate(auth_classes=[HeaderAuthentication])
208
+ def api_data():
209
+ return {"status": "ok"}
210
+
211
+ # 2. From Cookie
212
+ @app.route("/web/profile")
213
+ @authenticate(auth_classes=[CookieAuthentication])
214
+ def web_profile():
215
+ return {"status": "ok"}
216
+
217
+ # 3. From URL parameter (e.g., SSE or WebSocket connection handshake)
218
+ @app.route("/stream")
219
+ @authenticate(auth_classes=[QueryParamAuthentication])
220
+ def stream():
221
+ return {"status": "connected"}
222
+ ```
223
+
224
+ ---
225
+
226
+ ## ๐Ÿ—„๏ธ Database Model Mixins
227
+
228
+ When enabling `track_created` or `blacklisted`, use our ready-to-use SQLAlchemy model mixins:
229
+
230
+ ```python
231
+ from flask_advanced_auth.models import AbstractTrackModelMixin, AbstractBlackListModelMixin
232
+ from myapp.database import db
233
+
234
+ class TrackedToken(db.Model, AbstractTrackModelMixin):
235
+ __tablename__ = "tracked_tokens"
236
+
237
+ class BlacklistedToken(db.Model, AbstractBlackListModelMixin):
238
+ __tablename__ = "blacklisted_tokens"
239
+ ```
240
+
241
+ ---
242
+
243
+ ## ๐Ÿงช Running Tests
244
+
245
+ ```bash
246
+ # Run tests with coverage
247
+ pytest -v
248
+
249
+ # Run single test module
250
+ pytest tests/test_decorators.py -v
251
+ ```
252
+
253
+ ---
254
+
255
+ ## ๐Ÿค Contributing
256
+
257
+ Contributions are welcome! Please check out [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions and guidelines.
258
+
259
+ ---
260
+
261
+ ## ๐Ÿ“„ License
262
+
263
+ This project is licensed under the **MIT License** โ€” see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,223 @@
1
+ # flask-advanced-auth
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/flask-advanced-auth.svg)](https://pypi.org/project/flask-advanced-auth/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/flask-advanced-auth.svg)](https://pypi.org/project/flask-advanced-auth/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
+ [![Tests](https://github.com/Wehda23/Flask-Advanced-Authentication/actions/workflows/ci.yml/badge.svg)](https://github.com/Wehda23/Flask-Advanced-Authentication/actions)
7
+ [![Coverage](https://img.shields.io/badge/coverage-88%25-brightgreen.svg)](https://github.com/Wehda23/Flask-Advanced-Authentication)
8
+
9
+ **flask-advanced-auth** is a production-grade, highly configurable JWT authentication extension for Flask and Flask-SQLAlchemy. It provides multi-rule support (different token lifetimes, secrets, and models for users vs. admins), built-in database token tracking, automatic token revocation (blacklisting), and clean route decorators.
10
+
11
+ ---
12
+
13
+ ## โœจ Features
14
+
15
+ - ๐Ÿ”‘ **Multi-Rule Authentication**: Define distinct rules for different user tiers (e.g. `users`, `managers`, `api_clients`) with independent secrets, algorithms, and lifetimes.
16
+ - ๐Ÿ›ก๏ธ **Easy Route Protection**: Clean `@authenticate(auth_classes=[AuthenticateJWT])` decorator with customizable 401 response and model injection into `request.instance`.
17
+ - ๐Ÿ”„ **Access & Refresh Tokens**: Native support for short-lived access tokens and long-lived refresh tokens with convenient `refresh_access_token()` helper.
18
+ - ๐Ÿšซ **Token Revocation (Blacklisting)**: Built-in revocation mechanism with database persistence and `logout()` helper.
19
+ - ๐Ÿ“Š **Token Tracking**: Track issued tokens in the database to detect anomalies, enforce single-session limits, or audit activity.
20
+ - ๐Ÿ”Œ **Multiple Extraction Strategies**: Extract tokens from `Authorization: Bearer <token>` headers, HTTP cookies, or URL query parameters.
21
+ - โšก **Pydantic Validation**: Strict configuration schema validation powered by Pydantic V2 with flexible aliases.
22
+ - ๐Ÿงช **Thoroughly Tested**: 100% test pass rate with โ‰ฅ 88% test coverage.
23
+
24
+ ---
25
+
26
+ ## ๐Ÿ“ฆ Installation
27
+
28
+ ```bash
29
+ pip install flask-advanced-auth
30
+ ```
31
+
32
+ Or install with development dependencies:
33
+
34
+ ```bash
35
+ pip install flask-advanced-auth[dev]
36
+ ```
37
+
38
+ ---
39
+
40
+ ## ๐Ÿš€ Quick Start (5 Minutes)
41
+
42
+ ### 1. Configure Rules & Bootstrap
43
+
44
+ ```python
45
+ from datetime import timedelta
46
+ from flask import Flask, jsonify, request
47
+ from flask_sqlalchemy import SQLAlchemy
48
+ from flask_advanced_auth import (
49
+ initialize_jwt_authentication,
50
+ authenticate,
51
+ AuthenticateJWT,
52
+ TokenFactory,
53
+ refresh_access_token,
54
+ logout,
55
+ )
56
+
57
+ app = Flask(__name__)
58
+ app.config["SECRET_KEY"] = "your-app-secret"
59
+ app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db"
60
+ db = SQLAlchemy(app)
61
+
62
+ # Define your JWT rules
63
+ JWT_RULES = {
64
+ "default": {
65
+ "description": "Standard user access token",
66
+ "secret_key": "your-jwt-secret-key",
67
+ "algorithm": "HS256",
68
+ "access_token_lifetime": timedelta(minutes=15),
69
+ "refresh_token_lifetime": timedelta(days=7),
70
+ "token_header": "Bearer ",
71
+ }
72
+ }
73
+
74
+ # Initialize flask-advanced-auth
75
+ initialize_jwt_authentication(
76
+ database_path="app.db",
77
+ rules=JWT_RULES,
78
+ )
79
+ ```
80
+
81
+ ### 2. Issue Tokens on Login
82
+
83
+ ```python
84
+ @app.route("/api/login", methods=["POST"])
85
+ def login():
86
+ username = request.json.get("username")
87
+
88
+ factory = TokenFactory()
89
+ access_token = factory.generate_token(
90
+ rule_name="default",
91
+ extra_payload={"sub": username},
92
+ )
93
+ refresh_token = factory.generate_refresh_token(
94
+ rule_name="default",
95
+ extra_payload={"sub": username},
96
+ )
97
+
98
+ return jsonify({
99
+ "access_token": access_token,
100
+ "refresh_token": refresh_token,
101
+ })
102
+ ```
103
+
104
+ ### 3. Protect Routes
105
+
106
+ ```python
107
+ @app.route("/api/dashboard")
108
+ @authenticate(auth_classes=[AuthenticateJWT], rule_name="default")
109
+ def dashboard():
110
+ return jsonify({"message": "Welcome to your protected dashboard!"})
111
+ ```
112
+
113
+ ### 4. Refresh Tokens & Logout
114
+
115
+ ```python
116
+ @app.route("/api/refresh", methods=["POST"])
117
+ def refresh():
118
+ token = request.json.get("refresh_token")
119
+ new_access_token = refresh_access_token(token, rule_name="default")
120
+ return jsonify({"access_token": new_access_token})
121
+
122
+ @app.route("/api/logout", methods=["POST"])
123
+ def user_logout():
124
+ token = request.headers.get("Authorization", "").replace("Bearer ", "")
125
+ logout(token, rule_name="default")
126
+ return jsonify({"message": "Token revoked successfully."})
127
+ ```
128
+
129
+ ---
130
+
131
+ ## โš™๏ธ Configuration Reference
132
+
133
+ Rules are passed as a dictionary where each key represents a rule name (e.g. `"default"`, `"admin"`):
134
+
135
+ | Field | Type | Default | Description |
136
+ |---|---|---|---|
137
+ | `description` | `str` | *Required* | Description of what this token rule is used for. |
138
+ | `secret_key` | `str` | *Required* | Secret signing key for encoding and decoding tokens. |
139
+ | `algorithm` | `str` | *Required* | JWT signing algorithm (e.g. `'HS256'`, `'RS256'`). |
140
+ | `access_token_lifetime` | `timedelta` | *Required* | Expiry duration for access tokens. |
141
+ | `refresh_token_lifetime` | `timedelta` | `None` | Expiry duration for refresh tokens. |
142
+ | `token_header` | `str` | `'Bearer '` | Expected header prefix in `Authorization` header. |
143
+ | `table` | `bool` | `False` | Whether to link tokens to a database entity. |
144
+ | `table_path` | `str` | `None` | Dotted import path to model class (e.g. `'myapp.models.User'`). |
145
+ | `track_created` | `bool` | `False` | Persist issued tokens to the database. |
146
+ | `track_created_table_path` | `str` | `None` | Dotted import path to TrackModel class. |
147
+ | `track_created_allow_duplicates` | `bool` | `True` | Allow multiple active tokens for the same user. |
148
+ | `blacklisted` | `bool` | `False` | Enable token blacklist checks on verification. |
149
+ | `blacklisted_table_path` | `str` | `None` | Dotted import path to BlackListModel class. |
150
+
151
+ ---
152
+
153
+ ## ๐Ÿงฉ Authentication Strategies
154
+
155
+ You can choose how tokens are extracted by passing different authentication classes to `@authenticate`:
156
+
157
+ ```python
158
+ from flask_advanced_auth import (
159
+ authenticate,
160
+ HeaderAuthentication, # Reads Authorization: Bearer <token>
161
+ CookieAuthentication, # Reads token from HTTP cookies
162
+ QueryParamAuthentication, # Reads ?token=<token>
163
+ )
164
+
165
+ # 1. From Authorization header
166
+ @app.route("/api/v1/data")
167
+ @authenticate(auth_classes=[HeaderAuthentication])
168
+ def api_data():
169
+ return {"status": "ok"}
170
+
171
+ # 2. From Cookie
172
+ @app.route("/web/profile")
173
+ @authenticate(auth_classes=[CookieAuthentication])
174
+ def web_profile():
175
+ return {"status": "ok"}
176
+
177
+ # 3. From URL parameter (e.g., SSE or WebSocket connection handshake)
178
+ @app.route("/stream")
179
+ @authenticate(auth_classes=[QueryParamAuthentication])
180
+ def stream():
181
+ return {"status": "connected"}
182
+ ```
183
+
184
+ ---
185
+
186
+ ## ๐Ÿ—„๏ธ Database Model Mixins
187
+
188
+ When enabling `track_created` or `blacklisted`, use our ready-to-use SQLAlchemy model mixins:
189
+
190
+ ```python
191
+ from flask_advanced_auth.models import AbstractTrackModelMixin, AbstractBlackListModelMixin
192
+ from myapp.database import db
193
+
194
+ class TrackedToken(db.Model, AbstractTrackModelMixin):
195
+ __tablename__ = "tracked_tokens"
196
+
197
+ class BlacklistedToken(db.Model, AbstractBlackListModelMixin):
198
+ __tablename__ = "blacklisted_tokens"
199
+ ```
200
+
201
+ ---
202
+
203
+ ## ๐Ÿงช Running Tests
204
+
205
+ ```bash
206
+ # Run tests with coverage
207
+ pytest -v
208
+
209
+ # Run single test module
210
+ pytest tests/test_decorators.py -v
211
+ ```
212
+
213
+ ---
214
+
215
+ ## ๐Ÿค Contributing
216
+
217
+ Contributions are welcome! Please check out [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions and guidelines.
218
+
219
+ ---
220
+
221
+ ## ๐Ÿ“„ License
222
+
223
+ This project is licensed under the **MIT License** โ€” see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+ """
3
+ Module contains proxy class for JWT token encoding and decoding.
4
+ """
5
+
6
+ from abc import ABC, abstractmethod
7
+ from typing import Any, Optional
8
+ import jwt
9
+ from .jwt_rules import JWTAuthenticationRules
10
+ from datetime import datetime, timezone, timedelta
11
+
12
+
13
+ class JsonWebToken(ABC):
14
+ """Abstract base class for JWT encode/decode operations (Proxy pattern)."""
15
+
16
+ @abstractmethod
17
+ def encode_token(self, payload: dict, secret_key: str, algorithm: str) -> str:
18
+ """Encode a payload into a JWT token string."""
19
+ pass
20
+
21
+ @abstractmethod
22
+ def decode_token(self, token: str, secret_key: str, algorithm: str) -> Optional[dict]:
23
+ """Decode a JWT token string into a payload dict."""
24
+ pass
25
+
26
+
27
+ class PyJWT(JsonWebToken):
28
+ """Concrete JWT proxy using the PyJWT library."""
29
+
30
+ @property
31
+ def rule_handler(self) -> JWTAuthenticationRules:
32
+ return JWTAuthenticationRules()
33
+
34
+ def required_payload(self, rule_name: str, token_type: str) -> dict[str, Any]:
35
+ """Build the standard required payload fields (type + expiry)."""
36
+ token_lifetime: timedelta = self.rule_handler.get_token_lifetime(
37
+ rule_name, token_type
38
+ )
39
+ return {
40
+ "type": token_type,
41
+ "exp": datetime.now(timezone.utc) + token_lifetime,
42
+ }
43
+
44
+ def encode_token(
45
+ self,
46
+ payload: dict,
47
+ secret_key: str,
48
+ algorithm: str,
49
+ *args: Any,
50
+ **kwargs: Any,
51
+ ) -> str:
52
+ """Encode a payload dict into a signed JWT string."""
53
+ return jwt.encode(payload, key=secret_key, algorithm=algorithm, *args, **kwargs)
54
+
55
+ def decode_token(
56
+ self,
57
+ token: str,
58
+ secret_key: str,
59
+ algorithm: str,
60
+ *args: Any,
61
+ **kwargs: Any,
62
+ ) -> Optional[dict]:
63
+ """Decode and verify a JWT token."""
64
+ try:
65
+ return jwt.decode(
66
+ token, key=secret_key, algorithms=[algorithm], *args, **kwargs
67
+ )
68
+ except (jwt.ExpiredSignatureError, jwt.InvalidTokenError):
69
+ return None