Skip to content

Commit c77d756

Browse files
indrajeet307Indrajeet Khandekar
and
Indrajeet Khandekar
authored
Allow selective disabling of blocklist check (#501)
* Allow selective disabling of blocklist check Fixes #499 * Update the documentation --------- Co-authored-by: Indrajeet Khandekar <[email protected]>
1 parent 0803dc5 commit c77d756

File tree

2 files changed

+79
-3
lines changed

2 files changed

+79
-3
lines changed

flask_jwt_extended/view_decorators.py

+27-3
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def verify_jwt_in_request(
4848
refresh: bool = False,
4949
locations: Optional[LocationType] = None,
5050
verify_type: bool = True,
51+
skip_revocation_check: bool = False,
5152
) -> Optional[Tuple[dict, dict]]:
5253
"""
5354
Verify that a valid JWT is present in the request, unless ``optional=True`` in
@@ -76,6 +77,14 @@ def verify_jwt_in_request(
7677
to the ``refresh`` argument. If ``False``, type will not be checked and both
7778
access and refresh tokens will be accepted.
7879
80+
:param skip_revocation_check:
81+
If ``True``, revocation status of the token will be *not* checked. If ``False``,
82+
revocation status of the token will be checked.
83+
84+
:param skip_revocation_check:
85+
If ``True``, revocation status of the token will be *not* checked. If ``False``,
86+
revocation status of the token will be checked.
87+
7988
:return:
8089
A tuple containing the jwt_header and the jwt_data if a valid JWT is
8190
present in the request. If ``optional=True`` and no JWT is in the request,
@@ -87,7 +96,11 @@ def verify_jwt_in_request(
8796

8897
try:
8998
jwt_data, jwt_header, jwt_location = _decode_jwt_from_request(
90-
locations, fresh, refresh=refresh, verify_type=verify_type
99+
locations,
100+
fresh,
101+
refresh=refresh,
102+
verify_type=verify_type,
103+
skip_revocation_check=skip_revocation_check,
91104
)
92105

93106
except NoAuthorizationError:
@@ -115,6 +128,7 @@ def jwt_required(
115128
refresh: bool = False,
116129
locations: Optional[LocationType] = None,
117130
verify_type: bool = True,
131+
skip_revocation_check: bool = False,
118132
) -> Any:
119133
"""
120134
A decorator to protect a Flask endpoint with JSON Web Tokens.
@@ -145,12 +159,18 @@ def jwt_required(
145159
If ``True``, the token type (access or refresh) will be checked according
146160
to the ``refresh`` argument. If ``False``, type will not be checked and both
147161
access and refresh tokens will be accepted.
162+
163+
:param skip_revocation_check:
164+
If ``True``, revocation status of the token will be *not* checked. If ``False``,
165+
revocation status of the token will be checked.
148166
"""
149167

150168
def wrapper(fn):
151169
@wraps(fn)
152170
def decorator(*args, **kwargs):
153-
verify_jwt_in_request(optional, fresh, refresh, locations, verify_type)
171+
verify_jwt_in_request(
172+
optional, fresh, refresh, locations, verify_type, skip_revocation_check
173+
)
154174
return current_app.ensure_sync(fn)(*args, **kwargs)
155175

156176
return decorator
@@ -284,6 +304,7 @@ def _decode_jwt_from_request(
284304
fresh: bool,
285305
refresh: bool = False,
286306
verify_type: bool = True,
307+
skip_revocation_check: bool = False,
287308
) -> Tuple[dict, dict, str]:
288309
# Figure out what locations to look for the JWT in this request
289310
if isinstance(locations, str):
@@ -346,7 +367,10 @@ def _decode_jwt_from_request(
346367

347368
if fresh:
348369
_verify_token_is_fresh(jwt_header, decoded_token)
349-
verify_token_not_blocklisted(jwt_header, decoded_token)
370+
371+
if not skip_revocation_check:
372+
verify_token_not_blocklisted(jwt_header, decoded_token)
373+
350374
custom_verification_for_token(jwt_header, decoded_token)
351375

352376
return decoded_token, jwt_header, jwt_location

tests/test_blocklist.py

+52
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ def app():
2121
def access_protected():
2222
return jsonify(foo="bar")
2323

24+
@app.route("/protected_skip_blocklist", methods=["GET"])
25+
@jwt_required(verify_type=False, skip_revocation_check=True)
26+
def access_protected_skip_blocklist():
27+
return jsonify(foo="bar")
28+
29+
@app.route("/protected_noskip_blocklist", methods=["GET"])
30+
@jwt_required(verify_type=False)
31+
def access_protected_no_skip_blocklist():
32+
return jsonify(foo="bar")
33+
2434
@app.route("/refresh_protected", methods=["GET"])
2535
@jwt_required(refresh=True)
2636
def refresh_protected():
@@ -29,6 +39,48 @@ def refresh_protected():
2939
return app
3040

3141

42+
@pytest.mark.parametrize("blocklist_type", [["access"], ["refresh", "access"]])
43+
def test_blocklisted_access_token_revocation_skip(app, blocklist_type):
44+
jwt = get_jwt_manager(app)
45+
46+
@jwt.token_in_blocklist_loader
47+
def check_blocklisted(jwt_header, jwt_data):
48+
assert jwt_header["alg"] == "HS256"
49+
assert jwt_data["sub"] == "username"
50+
return True
51+
52+
with app.test_request_context():
53+
access_token = create_access_token("username")
54+
55+
test_client = app.test_client()
56+
response = test_client.get(
57+
"/protected_skip_blocklist", headers=make_headers(access_token)
58+
)
59+
assert response.get_json() == {"foo": "bar"}
60+
assert response.status_code == 200
61+
62+
63+
@pytest.mark.parametrize("blocklist_type", [["access"], ["refresh", "access"]])
64+
def test_blocklisted_access_token_revocation_no_skip(app, blocklist_type):
65+
jwt = get_jwt_manager(app)
66+
67+
@jwt.token_in_blocklist_loader
68+
def check_blocklisted(jwt_header, jwt_data):
69+
assert jwt_header["alg"] == "HS256"
70+
assert jwt_data["sub"] == "username"
71+
return True
72+
73+
with app.test_request_context():
74+
access_token = create_access_token("username")
75+
76+
test_client = app.test_client()
77+
response = test_client.get(
78+
"/protected_noskip_blocklist", headers=make_headers(access_token)
79+
)
80+
assert response.get_json() == {"msg": "Token has been revoked"}
81+
assert response.status_code == 401
82+
83+
3284
@pytest.mark.parametrize("blocklist_type", [["access"], ["refresh", "access"]])
3385
def test_non_blocklisted_access_token(app, blocklist_type):
3486
jwt = get_jwt_manager(app)

0 commit comments

Comments
 (0)