Skip to content

Commit c3ac3db

Browse files
authored
Add controls for verify_sub option (#562)
1 parent 49cf2e9 commit c3ac3db

File tree

5 files changed

+51
-1
lines changed

5 files changed

+51
-1
lines changed

docs/options.rst

+13
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Overview:
2929
* `JWT_REFRESH_TOKEN_EXPIRES`_
3030
* `JWT_SECRET_KEY`_
3131
* `JWT_TOKEN_LOCATION`_
32+
* `JWT_VERIFY_SUB`_
3233

3334
- `Header Options:`_
3435

@@ -255,6 +256,18 @@ General Options:
255256

256257
Default: ``"headers"``
257258

259+
.. _JWT_VERIFY_SUB:
260+
.. py:data:: JWT_VERIFY_SUB
261+
262+
Control whether the ``sub`` claim is verified during JWT decoding.
263+
264+
The ``sub`` claim MUST be a ``str`` according the the JWT spec. Setting this option
265+
to ``True`` (the default) will enforce this requirement, and consider non-compliant
266+
JWTs invalid. Setting the option to ``False`` will skip this validation of the type
267+
of the ``sub`` claim, allowing any type for the ``sub`` claim to be considered valid.
268+
269+
Default: ``True``
270+
258271

259272
Header Options:
260273
~~~~~~~~~~~~~~~

flask_jwt_extended/config.py

+4
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,10 @@ def decode_issuer(self) -> str:
307307
def leeway(self) -> int:
308308
return current_app.config["JWT_DECODE_LEEWAY"]
309309

310+
@property
311+
def verify_sub(self) -> bool:
312+
return current_app.config["JWT_VERIFY_SUB"]
313+
310314
@property
311315
def encode_nbf(self) -> bool:
312316
return current_app.config["JWT_ENCODE_NBF"]

flask_jwt_extended/jwt_manager.py

+2
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ def _set_default_configuration_options(app: Flask) -> None:
228228
app.config.setdefault("JWT_SECRET_KEY", None)
229229
app.config.setdefault("JWT_SESSION_COOKIE", True)
230230
app.config.setdefault("JWT_TOKEN_LOCATION", ("headers",))
231+
app.config.setdefault("JWT_VERIFY_SUB", True)
231232
app.config.setdefault("JWT_ENCODE_NBF", True)
232233

233234
def additional_claims_loader(self, callback: Callable) -> Callable:
@@ -549,6 +550,7 @@ def _decode_jwt_from_config(
549550
"leeway": config.leeway,
550551
"secret": secret,
551552
"verify_aud": config.decode_audience is not None,
553+
"verify_sub": config.verify_sub,
552554
}
553555

554556
try:

flask_jwt_extended/tokens.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,9 @@ def _decode_jwt(
8585
leeway: int,
8686
secret: str,
8787
verify_aud: bool,
88+
verify_sub: bool,
8889
) -> dict:
89-
options = {"verify_aud": verify_aud}
90+
options = {"verify_aud": verify_aud, "verify_sub": verify_sub}
9091
if allow_expired:
9192
options["verify_exp"] = False
9293

tests/test_decode_tokens.py

+30
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from jwt import InvalidIssuerError
1313
from jwt import InvalidSignatureError
1414
from jwt import MissingRequiredClaimError
15+
from jwt.exceptions import InvalidSubjectError
1516

1617
from flask_jwt_extended import create_access_token
1718
from flask_jwt_extended import create_refresh_token
@@ -279,6 +280,35 @@ def test_verify_no_aud(app, default_access_token, token_aud):
279280
assert decoded["aud"] == token_aud
280281

281282

283+
@pytest.mark.parametrize("token_sub", [123, {}, [], False])
284+
def test_invalid_sub_values(app, default_access_token, token_sub):
285+
"""Verifies that invalid values for the sub claim fail decoding, the
286+
default behavior of JWT_VERIFY_SUB = True
287+
"""
288+
289+
default_access_token["sub"] = token_sub
290+
invalid_token = encode_token(app, default_access_token)
291+
with pytest.raises(InvalidSubjectError):
292+
with app.test_request_context():
293+
decode_token(invalid_token)
294+
295+
296+
@pytest.mark.parametrize("token_sub", [123, {}, [], False])
297+
def test_invalid_sub_values_allowed_with_no_verify(
298+
app, default_access_token, token_sub
299+
):
300+
"""Verifies that invalid values for the sub claim succeed at decoding, if
301+
the user configures JWT_VERIFY_SUB = False
302+
"""
303+
304+
app.config["JWT_VERIFY_SUB"] = False
305+
default_access_token["sub"] = token_sub
306+
valid_token = encode_token(app, default_access_token)
307+
with app.test_request_context():
308+
decoded = decode_token(valid_token)
309+
assert decoded["sub"] == token_sub
310+
311+
282312
def test_encode_iss(app, default_access_token):
283313
app.config["JWT_ENCODE_ISSUER"] = "foobar"
284314

0 commit comments

Comments
 (0)