Skip to content

Commit 2d30a76

Browse files
committed
Fix issue #23: Improve Blueprint JWT support
- Improved error message in jwt.py with specific guidance for Blueprint usage - Added comprehensive example (examples/routing/blueprint_with_jwt.py) showing correct JWT + Blueprint pattern - Clarified that JWT must be initialized on the main app, not in blueprint files - Updated CHANGELOG.md with fixes for issues #22 and #23 Resolves #23
1 parent 8cdf2a5 commit 2d30a76

3 files changed

Lines changed: 176 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22

33
All notable changes to this project will be documented here.
44

5+
## [0.10.3] - 2026-02-14
6+
7+
### Fixed
8+
9+
- **Request Parameter Injection** ([#22](https://github.com/GrandpaEJ/BustAPI/issues/22)):
10+
- Fixed route handlers not receiving the `request` parameter when declared in their signature.
11+
- Added automatic request parameter injection in both sync and async wrapper functions.
12+
- JWT authentication endpoints now work correctly with request-dependent handlers.
13+
14+
- **Blueprint JWT Support** ([#23](https://github.com/GrandpaEJ/BustAPI/issues/23)):
15+
- Improved error message when JWT is not initialized, with specific guidance for Blueprint usage.
16+
- Added comprehensive example (`examples/routing/blueprint_with_jwt.py`) showing correct JWT + Blueprint pattern.
17+
- Clarified that JWT must be initialized on the main app, not in blueprint files.
18+
519
## [0.10.2] - 2026-02-10
620

721
### Refactoring
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
"""
2+
Example: JWT Authentication with Blueprints
3+
4+
This example demonstrates the CORRECT way to use JWT with Blueprints in BustAPI.
5+
6+
IMPORTANT: Initialize JWT on the main app, not in the blueprint file!
7+
8+
Run:
9+
python examples/routing/blueprint_with_jwt.py
10+
11+
Test with:
12+
# Login
13+
curl -X POST http://localhost:5000/auth/login \
14+
-H "Content-Type: application/json" \
15+
-d '{"username": "admin", "password": "secret123"}'
16+
17+
# Access protected route
18+
curl http://localhost:5000/auth/protected \
19+
-H "Authorization: Bearer <token>"
20+
21+
# Refresh token
22+
curl -X POST http://localhost:5000/auth/refresh \
23+
-H "Authorization: Bearer <refresh_token>"
24+
"""
25+
26+
from bustapi import (
27+
JWT,
28+
Blueprint,
29+
BustAPI,
30+
hash_password,
31+
jwt_refresh_token_required,
32+
jwt_required,
33+
request,
34+
verify_password,
35+
)
36+
37+
# ============================================================================
38+
# BLUEPRINT DEFINITION (auth_routes.py in a real app)
39+
# ============================================================================
40+
41+
# Create the blueprint
42+
auth_bp = Blueprint(name="auth", import_name=__name__, url_prefix="/auth")
43+
44+
# Simulated user database
45+
USERS = {
46+
"admin": hash_password("secret123"),
47+
"user": hash_password("password"),
48+
}
49+
50+
51+
@auth_bp.post("/login")
52+
def login():
53+
"""Login and get access + refresh tokens."""
54+
data = request.json or {}
55+
username = data.get("username")
56+
password = data.get("password")
57+
58+
if not username or not password:
59+
return {"error": "Missing username or password"}, 400
60+
61+
stored_hash = USERS.get(username)
62+
if not stored_hash or not verify_password(password, stored_hash):
63+
return {"error": "Invalid credentials"}, 401
64+
65+
# Access JWT from the current app's extensions
66+
# This works because JWT was initialized on the main app
67+
jwt_ext = request.app.extensions.get("jwt")
68+
69+
# Create tokens
70+
access_token = jwt_ext.create_access_token(identity=username, fresh=True)
71+
refresh_token = jwt_ext.create_refresh_token(identity=username)
72+
73+
return {
74+
"access_token": access_token,
75+
"refresh_token": refresh_token,
76+
"token_type": "bearer",
77+
}
78+
79+
80+
@auth_bp.get("/protected")
81+
@jwt_required
82+
def protected():
83+
"""Protected route - requires valid JWT."""
84+
return {
85+
"message": f"Hello, {request.jwt_identity}!",
86+
"claims": request.jwt_claims,
87+
}
88+
89+
90+
@auth_bp.get("/fresh-only")
91+
@jwt_required
92+
def fresh_only():
93+
"""Route that requires a fresh token (from login, not refresh)."""
94+
if not request.jwt_claims.get("fresh", False):
95+
return {"error": "Fresh token required"}, 401
96+
97+
return {
98+
"message": "This route requires a fresh token",
99+
"user": request.jwt_identity,
100+
}
101+
102+
103+
@auth_bp.post("/refresh")
104+
@jwt_refresh_token_required
105+
def refresh():
106+
"""Get a new access token using refresh token."""
107+
jwt_ext = request.app.extensions.get("jwt")
108+
109+
# Create new access token (not fresh since it's from refresh)
110+
new_access_token = jwt_ext.create_access_token(
111+
identity=request.jwt_identity,
112+
fresh=False,
113+
)
114+
115+
return {
116+
"access_token": new_access_token,
117+
"token_type": "bearer",
118+
}
119+
120+
121+
# ============================================================================
122+
# MAIN APPLICATION (main.py in a real app)
123+
# ============================================================================
124+
125+
if __name__ == "__main__":
126+
# Create the main application
127+
app = BustAPI(__name__)
128+
app.secret_key = "your-super-secret-key-change-in-production"
129+
130+
# ✅ CORRECT: Initialize JWT on the MAIN app
131+
jwt = JWT(app)
132+
133+
# Register the blueprint
134+
app.register_blueprint(auth_bp)
135+
136+
# Add a public route on the main app
137+
@app.get("/")
138+
def home():
139+
return {"message": "Welcome! Use /auth/login to get started"}
140+
141+
print("Blueprint JWT Authentication Example")
142+
print("-" * 40)
143+
print("Endpoints:")
144+
print(" GET / - Public home")
145+
print(" POST /auth/login - Get tokens")
146+
print(" GET /auth/protected - Requires JWT")
147+
print(" GET /auth/fresh-only - Requires fresh JWT")
148+
print(" POST /auth/refresh - Refresh access token")
149+
print("-" * 40)
150+
print()
151+
print("✅ CORRECT PATTERN:")
152+
print(" - JWT initialized on main app")
153+
print(" - Blueprint registered to main app")
154+
print(" - Routes access JWT via request.app.extensions['jwt']")
155+
print("-" * 40)
156+
157+
app.run(debug=True)

python/bustapi/jwt.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,11 @@ def _get_jwt() -> JWT:
190190
if jwt_ext:
191191
return jwt_ext
192192

193-
raise RuntimeError("No JWT instance found. Initialize JWT(app) first.")
193+
raise RuntimeError(
194+
"No JWT instance found. Initialize JWT(app) on your main application. "
195+
"When using Blueprints, initialize JWT on the main app in your entry point file, "
196+
"not in the blueprint definition file."
197+
)
194198

195199

196200
def _get_token_from_request() -> Optional[str]:

0 commit comments

Comments
 (0)