Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add controls for verify_sub option in PyJWT #562

Merged
merged 1 commit into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading