From 628f53ad46a65784154526b3e53cef9bbd1c4444 Mon Sep 17 00:00:00 2001 From: Lily Acadia Gilbert Date: Tue, 12 Dec 2023 22:05:20 -0700 Subject: [PATCH] Do not check JWT_TOKEN_LOCATION when setting csrf value in a jwt Previously, we would only include the csrf double submit value in a jwt if `JWT_COOKIE_CSRF_PROTECT` was true (the default) AND `JWT_TOKEN_LOCATION` was configured to use cookies. However, since we allow overwriting `locations` on a per-route basis instead of only globally for he whole application, we could create a situation where a single route was configured to use cookies when the rest of the app was not, and csrf checks were not happening against that endpoint. This change makes it so that any jwts will be encoded with a csrf value when `JWT_COOKIE_CSRF_PROTECT` is true, regardless of if the app is globally configured to use cookies. It will also verify the csrf double submit token on any route that uses cookies when `JWT_COOKIE_CSRF_PROTECT` is true, regardless of if that is set globally in the application or on an individual route. As a result of this change, you might notice that using jwts without cookies now include a csrf value. This will not change the behavior of non-jwt based endpoints at all, your jwts will just be a little bigger. You can remove that key from the jwt by explicitly setting `JWT_COOKIE_CSRF_PROTECT` to False, if you are not using cookies. --- flask_jwt_extended/config.py | 4 ++-- flask_jwt_extended/jwt_manager.py | 2 +- flask_jwt_extended/utils.py | 8 ++++---- flask_jwt_extended/view_decorators.py | 2 +- tests/test_config.py | 14 ++++---------- 5 files changed, 12 insertions(+), 18 deletions(-) diff --git a/flask_jwt_extended/config.py b/flask_jwt_extended/config.py index 9089b91f..11b16537 100644 --- a/flask_jwt_extended/config.py +++ b/flask_jwt_extended/config.py @@ -135,8 +135,8 @@ def refresh_json_key(self) -> str: return current_app.config["JWT_REFRESH_JSON_KEY"] @property - def csrf_protect(self) -> bool: - return self.jwt_in_cookies and current_app.config["JWT_COOKIE_CSRF_PROTECT"] + def cookie_csrf_protect(self) -> bool: + return current_app.config["JWT_COOKIE_CSRF_PROTECT"] @property def csrf_request_methods(self) -> Iterable[str]: diff --git a/flask_jwt_extended/jwt_manager.py b/flask_jwt_extended/jwt_manager.py index dfe45e0b..2bc00771 100644 --- a/flask_jwt_extended/jwt_manager.py +++ b/flask_jwt_extended/jwt_manager.py @@ -516,7 +516,7 @@ def _encode_jwt_from_config( algorithm=config.algorithm, audience=config.encode_audience, claim_overrides=claim_overrides, - csrf=config.csrf_protect, + csrf=config.cookie_csrf_protect, expires_delta=expires_delta, fresh=fresh, header_overrides=header_overrides, diff --git a/flask_jwt_extended/utils.py b/flask_jwt_extended/utils.py index 39ef65b6..8ddd2750 100644 --- a/flask_jwt_extended/utils.py +++ b/flask_jwt_extended/utils.py @@ -305,7 +305,7 @@ def set_access_cookies( samesite=config.cookie_samesite, ) - if config.csrf_protect and config.csrf_in_cookies: + if config.cookie_csrf_protect and config.csrf_in_cookies: response.set_cookie( config.access_csrf_cookie_name, value=get_csrf_token(encoded_access_token), @@ -358,7 +358,7 @@ def set_refresh_cookies( samesite=config.cookie_samesite, ) - if config.csrf_protect and config.csrf_in_cookies: + if config.cookie_csrf_protect and config.csrf_in_cookies: response.set_cookie( config.refresh_csrf_cookie_name, value=get_csrf_token(encoded_refresh_token), @@ -408,7 +408,7 @@ def unset_access_cookies(response: Response, domain: Optional[str] = None) -> No samesite=config.cookie_samesite, ) - if config.csrf_protect and config.csrf_in_cookies: + if config.cookie_csrf_protect and config.csrf_in_cookies: response.set_cookie( config.access_csrf_cookie_name, value="", @@ -446,7 +446,7 @@ def unset_refresh_cookies(response: Response, domain: Optional[str] = None) -> N samesite=config.cookie_samesite, ) - if config.csrf_protect and config.csrf_in_cookies: + if config.cookie_csrf_protect and config.csrf_in_cookies: response.set_cookie( config.refresh_csrf_cookie_name, value="", diff --git a/flask_jwt_extended/view_decorators.py b/flask_jwt_extended/view_decorators.py index b39a9395..407bea4d 100644 --- a/flask_jwt_extended/view_decorators.py +++ b/flask_jwt_extended/view_decorators.py @@ -244,7 +244,7 @@ def _decode_jwt_from_cookies(refresh: bool) -> Tuple[str, Optional[str]]: if not encoded_token: raise NoAuthorizationError('Missing cookie "{}"'.format(cookie_key)) - if config.csrf_protect and request.method in config.csrf_request_methods: + if config.cookie_csrf_protect and request.method in config.csrf_request_methods: csrf_value = request.headers.get(csrf_header_key, None) if not csrf_value and config.csrf_check_form: csrf_value = request.form.get(csrf_field_key, None) diff --git a/tests/test_config.py b/tests/test_config.py index 514d619e..f38bcfc4 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -51,7 +51,7 @@ def test_default_configs(app): assert config.json_key == "access_token" assert config.refresh_json_key == "refresh_token" - assert config.csrf_protect is False + assert config.cookie_csrf_protect is True assert config.csrf_request_methods == ["POST", "PUT", "PATCH", "DELETE"] assert config.csrf_in_cookies is True assert config.access_csrf_cookie_name == "csrf_access_token" @@ -142,7 +142,7 @@ def test_override_configs(app, delta_func): assert config.session_cookie is False assert config.cookie_samesite == "Strict" - assert config.csrf_protect is True + assert config.cookie_csrf_protect is True assert config.csrf_request_methods == ["GET"] assert config.csrf_in_cookies is False assert config.access_csrf_cookie_name == "access_csrf_cookie" @@ -333,17 +333,11 @@ def test_jwt_token_locations_config(app): def test_csrf_protect_config(app): with app.test_request_context(): - app.config["JWT_TOKEN_LOCATION"] = ["headers"] app.config["JWT_COOKIE_CSRF_PROTECT"] = True - assert config.csrf_protect is False + assert config.cookie_csrf_protect is True - app.config["JWT_TOKEN_LOCATION"] = ["cookies"] - app.config["JWT_COOKIE_CSRF_PROTECT"] = True - assert config.csrf_protect is True - - app.config["JWT_TOKEN_LOCATION"] = ["cookies"] app.config["JWT_COOKIE_CSRF_PROTECT"] = False - assert config.csrf_protect is False + assert config.cookie_csrf_protect is False def test_missing_algorithm_in_decode_algorithms(app):