Skip to content

Commit a43701f

Browse files
author
Landon Gilbert-Bland
committed
Add JWT_QUERY_STRING_VALUE_PREFIX configuration option
Refs #421
1 parent 7068f12 commit a43701f

8 files changed

+74
-8
lines changed

docs/options.rst

+11
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,17 @@ These are only applicable if a route is configured to accept JWTs via query stri
397397
Default: ``"jwt"``
398398

399399

400+
.. py:data:: JWT_QUERY_STRING_VALUE_PREFIX
401+
402+
An optional prefix string that should show up before the JWT in a
403+
query string parameter.
404+
405+
For example, if this was ``"Bearer "``, the query string should look like
406+
``"/endpoint?jwt=Bearer <JWT>"``
407+
408+
Default: ``""``
409+
410+
400411
JSON Body Options:
401412
~~~~~~~~~~~~~~~~~~
402413
These are only applicable if a route is configured to accept JWTs via the JSON body.

flask_jwt_extended/config.py

+4
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ def header_type(self):
8282
def query_string_name(self):
8383
return current_app.config["JWT_QUERY_STRING_NAME"]
8484

85+
@property
86+
def query_string_value_prefix(self):
87+
return current_app.config["JWT_QUERY_STRING_VALUE_PREFIX"]
88+
8589
@property
8690
def access_cookie_name(self):
8791
return current_app.config["JWT_ACCESS_COOKIE_NAME"]

flask_jwt_extended/exceptions.py

+8
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ class InvalidHeaderError(JWTExtendedException):
2222
pass
2323

2424

25+
class InvalidQueryParamError(JWTExtendedException):
26+
"""
27+
An error when a query string param is not in the correct format
28+
"""
29+
30+
pass
31+
32+
2533
class NoAuthorizationError(JWTExtendedException):
2634
"""
2735
An error raised when no authorization token was found in a protected endpoint

flask_jwt_extended/jwt_manager.py

+6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from flask_jwt_extended.exceptions import CSRFError
2929
from flask_jwt_extended.exceptions import FreshTokenRequired
3030
from flask_jwt_extended.exceptions import InvalidHeaderError
31+
from flask_jwt_extended.exceptions import InvalidQueryParamError
3132
from flask_jwt_extended.exceptions import JWTDecodeError
3233
from flask_jwt_extended.exceptions import NoAuthorizationError
3334
from flask_jwt_extended.exceptions import RevokedTokenError
@@ -142,6 +143,10 @@ def handle_jwt_decode_error(e):
142143
def handle_auth_error(e):
143144
return self._unauthorized_callback(str(e))
144145

146+
@app.errorhandler(InvalidQueryParamError)
147+
def handle_invalid_query_param_error(e):
148+
return self._invalid_token_callback(str(e))
149+
145150
@app.errorhandler(RevokedTokenError)
146151
def handle_revoked_token_error(e):
147152
return self._revoked_token_callback(e.jwt_header, e.jwt_data)
@@ -191,6 +196,7 @@ def _set_default_configuration_options(app):
191196
app.config.setdefault("JWT_PRIVATE_KEY", None)
192197
app.config.setdefault("JWT_PUBLIC_KEY", None)
193198
app.config.setdefault("JWT_QUERY_STRING_NAME", "jwt")
199+
app.config.setdefault("JWT_QUERY_STRING_VALUE_PREFIX", "")
194200
app.config.setdefault("JWT_REFRESH_COOKIE_NAME", "refresh_token_cookie")
195201
app.config.setdefault("JWT_REFRESH_COOKIE_PATH", "/")
196202
app.config.setdefault("JWT_REFRESH_CSRF_COOKIE_NAME", "csrf_refresh_token")

flask_jwt_extended/view_decorators.py

+14-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from flask_jwt_extended.exceptions import CSRFError
1212
from flask_jwt_extended.exceptions import FreshTokenRequired
1313
from flask_jwt_extended.exceptions import InvalidHeaderError
14+
from flask_jwt_extended.exceptions import InvalidQueryParamError
1415
from flask_jwt_extended.exceptions import NoAuthorizationError
1516
from flask_jwt_extended.exceptions import UserLookupError
1617
from flask_jwt_extended.internal_utils import custom_verification_for_token
@@ -207,11 +208,20 @@ def _decode_jwt_from_cookies(refresh):
207208

208209

209210
def _decode_jwt_from_query_string():
210-
query_param = config.query_string_name
211-
encoded_token = request.args.get(query_param)
212-
if not encoded_token:
213-
raise NoAuthorizationError('Missing "{}" query paramater'.format(query_param))
211+
param_name = config.query_string_name
212+
prefix = config.query_string_value_prefix
213+
214+
value = request.args.get(param_name)
215+
if not value:
216+
raise NoAuthorizationError(f"Missing '{param_name}' query paramater")
217+
218+
if not value.startswith(prefix):
219+
raise InvalidQueryParamError(
220+
f"Invalid value for query parameter '{param_name}'. "
221+
f"Expected the value to start with '{prefix}'"
222+
)
214223

224+
encoded_token = value[len(prefix) :] # noqa: E203
215225
return encoded_token, None
216226

217227

tests/test_config.py

+3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def test_default_configs(app):
3131
assert config.header_type == "Bearer"
3232

3333
assert config.query_string_name == "jwt"
34+
assert config.query_string_value_prefix == ""
3435

3536
assert config.access_cookie_name == "access_token_cookie"
3637
assert config.refresh_cookie_name == "refresh_token_cookie"
@@ -81,6 +82,7 @@ def test_override_configs(app, delta_func):
8182
app.config["JWT_ENCODE_ISSUER"] = "TestEncodeIssuer"
8283

8384
app.config["JWT_QUERY_STRING_NAME"] = "banana"
85+
app.config["JWT_QUERY_STRING_VALUE_PREFIX"] = "kiwi"
8486

8587
app.config["JWT_ACCESS_COOKIE_NAME"] = "new_access_cookie"
8688
app.config["JWT_REFRESH_COOKIE_NAME"] = "new_refresh_cookie"
@@ -130,6 +132,7 @@ class CustomJSONEncoder(JSONEncoder):
130132
assert config.encode_issuer == "TestEncodeIssuer"
131133

132134
assert config.query_string_name == "banana"
135+
assert config.query_string_value_prefix == "kiwi"
133136

134137
assert config.access_cookie_name == "new_access_cookie"
135138
assert config.refresh_cookie_name == "new_refresh_cookie"

tests/test_multiple_token_locations.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def test_json_access(app, app_with_locations):
113113
(
114114
"Missing JWT in json or query_string (Invalid "
115115
"content-type. Must be application/json.; "
116-
'Missing "jwt" query paramater)'
116+
"Missing 'jwt' query paramater)"
117117
),
118118
),
119119
],

tests/test_query_string.py

+27-3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,30 @@ def test_default_query_paramater(app):
3535
assert response.get_json() == {"foo": "bar"}
3636

3737

38+
def test_query_string_value_prefix(app):
39+
app.config["JWT_QUERY_STRING_VALUE_PREFIX"] = "bearer "
40+
test_client = app.test_client()
41+
42+
with app.test_request_context():
43+
access_token = create_access_token("username")
44+
45+
# Valid string prefix
46+
url = f"/protected?jwt=bearer {access_token}"
47+
response = test_client.get(url)
48+
assert response.status_code == 200
49+
assert response.get_json() == {"foo": "bar"}
50+
51+
# Invalid string prefix
52+
url = f"/protected?jwt={access_token}"
53+
response = test_client.get(url)
54+
error_msg = (
55+
"Invalid value for query parameter 'jwt'. "
56+
"Expected the value to start with 'bearer '"
57+
)
58+
assert response.status_code == 422
59+
assert response.get_json() == {"msg": error_msg}
60+
61+
3862
def test_custom_query_paramater(app):
3963
app.config["JWT_QUERY_STRING_NAME"] = "foo"
4064
test_client = app.test_client()
@@ -46,7 +70,7 @@ def test_custom_query_paramater(app):
4670
url = "/protected?jwt={}".format(access_token)
4771
response = test_client.get(url)
4872
assert response.status_code == 401
49-
assert response.get_json() == {"msg": 'Missing "foo" query paramater'}
73+
assert response.get_json() == {"msg": "Missing 'foo' query paramater"}
5074

5175
# Insure new query_string does work
5276
url = "/protected?foo={}".format(access_token)
@@ -65,13 +89,13 @@ def test_missing_query_paramater(app):
6589
# Insure no query paramaters doesn't give a response
6690
response = test_client.get("/protected")
6791
assert response.status_code == 401
68-
assert response.get_json() == {"msg": 'Missing "jwt" query paramater'}
92+
assert response.get_json() == {"msg": "Missing 'jwt' query paramater"}
6993

7094
# Insure headers don't work
7195
access_headers = {"Authorization": "Bearer {}".format(access_token)}
7296
response = test_client.get("/protected", headers=access_headers)
7397
assert response.status_code == 401
74-
assert response.get_json() == {"msg": 'Missing "jwt" query paramater'}
98+
assert response.get_json() == {"msg": "Missing 'jwt' query paramater"}
7599

76100
# Test custom response works
77101
@jwtM.unauthorized_loader

0 commit comments

Comments
 (0)