Skip to content

Commit

Permalink
Add controls for verify_sub option (#562)
Browse files Browse the repository at this point in the history
  • Loading branch information
jlucier authored Nov 20, 2024
1 parent 49cf2e9 commit c3ac3db
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 1 deletion.
13 changes: 13 additions & 0 deletions docs/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Overview:
* `JWT_REFRESH_TOKEN_EXPIRES`_
* `JWT_SECRET_KEY`_
* `JWT_TOKEN_LOCATION`_
* `JWT_VERIFY_SUB`_

- `Header Options:`_

Expand Down Expand Up @@ -255,6 +256,18 @@ General Options:

Default: ``"headers"``

.. _JWT_VERIFY_SUB:
.. py:data:: JWT_VERIFY_SUB
Control whether the ``sub`` claim is verified during JWT decoding.

The ``sub`` claim MUST be a ``str`` according the the JWT spec. Setting this option
to ``True`` (the default) will enforce this requirement, and consider non-compliant
JWTs invalid. Setting the option to ``False`` will skip this validation of the type
of the ``sub`` claim, allowing any type for the ``sub`` claim to be considered valid.

Default: ``True``


Header Options:
~~~~~~~~~~~~~~~
Expand Down
4 changes: 4 additions & 0 deletions flask_jwt_extended/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,10 @@ def decode_issuer(self) -> str:
def leeway(self) -> int:
return current_app.config["JWT_DECODE_LEEWAY"]

@property
def verify_sub(self) -> bool:
return current_app.config["JWT_VERIFY_SUB"]

@property
def encode_nbf(self) -> bool:
return current_app.config["JWT_ENCODE_NBF"]
Expand Down
2 changes: 2 additions & 0 deletions flask_jwt_extended/jwt_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ def _set_default_configuration_options(app: Flask) -> None:
app.config.setdefault("JWT_SECRET_KEY", None)
app.config.setdefault("JWT_SESSION_COOKIE", True)
app.config.setdefault("JWT_TOKEN_LOCATION", ("headers",))
app.config.setdefault("JWT_VERIFY_SUB", True)
app.config.setdefault("JWT_ENCODE_NBF", True)

def additional_claims_loader(self, callback: Callable) -> Callable:
Expand Down Expand Up @@ -549,6 +550,7 @@ def _decode_jwt_from_config(
"leeway": config.leeway,
"secret": secret,
"verify_aud": config.decode_audience is not None,
"verify_sub": config.verify_sub,
}

try:
Expand Down
3 changes: 2 additions & 1 deletion flask_jwt_extended/tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ def _decode_jwt(
leeway: int,
secret: str,
verify_aud: bool,
verify_sub: bool,
) -> dict:
options = {"verify_aud": verify_aud}
options = {"verify_aud": verify_aud, "verify_sub": verify_sub}
if allow_expired:
options["verify_exp"] = False

Expand Down
30 changes: 30 additions & 0 deletions tests/test_decode_tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from jwt import InvalidIssuerError
from jwt import InvalidSignatureError
from jwt import MissingRequiredClaimError
from jwt.exceptions import InvalidSubjectError

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


@pytest.mark.parametrize("token_sub", [123, {}, [], False])
def test_invalid_sub_values(app, default_access_token, token_sub):
"""Verifies that invalid values for the sub claim fail decoding, the
default behavior of JWT_VERIFY_SUB = True
"""

default_access_token["sub"] = token_sub
invalid_token = encode_token(app, default_access_token)
with pytest.raises(InvalidSubjectError):
with app.test_request_context():
decode_token(invalid_token)


@pytest.mark.parametrize("token_sub", [123, {}, [], False])
def test_invalid_sub_values_allowed_with_no_verify(
app, default_access_token, token_sub
):
"""Verifies that invalid values for the sub claim succeed at decoding, if
the user configures JWT_VERIFY_SUB = False
"""

app.config["JWT_VERIFY_SUB"] = False
default_access_token["sub"] = token_sub
valid_token = encode_token(app, default_access_token)
with app.test_request_context():
decoded = decode_token(valid_token)
assert decoded["sub"] == token_sub


def test_encode_iss(app, default_access_token):
app.config["JWT_ENCODE_ISSUER"] = "foobar"

Expand Down

0 comments on commit c3ac3db

Please sign in to comment.