From 09ac2dfffadda808e74b80a023d92de43528e56d Mon Sep 17 00:00:00 2001 From: Craig Wright Date: Tue, 4 Apr 2017 17:05:46 -0700 Subject: [PATCH] Iss #35 Added a config option to control setting session or persistent cookies. The options getters seem to be merging app config and default config as transparently as possible so I added a helper method in utils.py, _get_cookie_max_age, to translate the true/false value into something meaningful for flask's set_cookie max_age parameter. Tests have been run on Python 2.7 only! This has also been installed on a flask app and tested manually. --- docs/options.rst | 2 ++ flask_jwt_extended/config.py | 5 +++++ flask_jwt_extended/utils.py | 14 +++++++++++++- tests/test_config.py | 5 ++++- 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/docs/options.rst b/docs/options.rst index dd635348..bb9b2d02 100644 --- a/docs/options.rst +++ b/docs/options.rst @@ -32,6 +32,8 @@ The available options are: ``JWT_REFRESH_COOKIE_PATH`` What ``path`` should be set for the refresh cookie. Defaults to ``None``, which will cause this access cookie to be sent in with every request. Should be modified for only the paths that need the refresh cookie +``JWT_SESSION_COOKIE`` Whether to set session (deleted when the browser is closed) or persistent cookies. + Defaults to ``True`` (sets session cookies). ``JWT_COOKIE_CSRF_PROTECT`` Enable/disable CSRF protection. Only used when sending the JWT in via cookies ``JWT_CSRF_METHODS`` The request types that will use CSRF protection. Defaults to ```['POST', 'PUT', 'PATCH', 'DELETE']``` diff --git a/flask_jwt_extended/config.py b/flask_jwt_extended/config.py index 815a4192..b6d3771f 100644 --- a/flask_jwt_extended/config.py +++ b/flask_jwt_extended/config.py @@ -15,6 +15,7 @@ REFRESH_COOKIE_NAME = 'refresh_token_cookie' ACCESS_COOKIE_PATH = None REFRESH_COOKIE_PATH = None +SESSION_COOKIE = True # True to use session cookies, False to use persistent # Options for using double submit for verifying CSRF tokens COOKIE_CSRF_PROTECT = True @@ -76,6 +77,10 @@ def get_refresh_cookie_path(): return current_app.config.get('JWT_REFRESH_COOKIE_PATH', REFRESH_COOKIE_PATH) +def get_session_cookie(): + return current_app.config.get('JWT_SESSION_COOKIE', SESSION_COOKIE) + + def get_cookie_csrf_protect(): return current_app.config.get('JWT_COOKIE_CSRF_PROTECT', COOKIE_CSRF_PROTECT) diff --git a/flask_jwt_extended/utils.py b/flask_jwt_extended/utils.py index 6479399e..1dc31b91 100644 --- a/flask_jwt_extended/utils.py +++ b/flask_jwt_extended/utils.py @@ -16,7 +16,7 @@ get_algorithm, get_blacklist_enabled, get_blacklist_checks, get_jwt_header_type, \ get_access_cookie_name, get_cookie_secure, get_access_cookie_path, \ get_cookie_csrf_protect, get_access_csrf_cookie_name, \ - get_refresh_cookie_name, get_refresh_cookie_path, \ + get_refresh_cookie_name, get_refresh_cookie_path, get_session_cookie, \ get_refresh_csrf_cookie_name, get_token_location, \ get_csrf_header_name, get_jwt_header_name, get_csrf_request_methods from flask_jwt_extended.exceptions import JWTEncodeError, JWTDecodeError, \ @@ -49,6 +49,14 @@ def get_raw_jwt(): return getattr(ctx_stack.top, 'jwt', {}) +def _get_cookie_max_age(): + """ + Checks config value for using session or persistent cookies and returns the + appropriate value for flask set_cookies. + """ + return None if get_session_cookie() else 2147483647 # 2^31 + + def _create_csrf_token(): return str(uuid.uuid4()) @@ -395,6 +403,7 @@ def set_access_cookies(response, encoded_access_token): # Set the access JWT in the cookie response.set_cookie(get_access_cookie_name(), value=encoded_access_token, + max_age=_get_cookie_max_age(), secure=get_cookie_secure(), httponly=True, path=get_access_cookie_path()) @@ -403,6 +412,7 @@ def set_access_cookies(response, encoded_access_token): if get_cookie_csrf_protect(): response.set_cookie(get_access_csrf_cookie_name(), value=_get_csrf_token(encoded_access_token), + max_age=_get_cookie_max_age(), secure=get_cookie_secure(), httponly=False, path='/') @@ -420,6 +430,7 @@ def set_refresh_cookies(response, encoded_refresh_token): # Set the refresh JWT in the cookie response.set_cookie(get_refresh_cookie_name(), value=encoded_refresh_token, + max_age=_get_cookie_max_age(), secure=get_cookie_secure(), httponly=True, path=get_refresh_cookie_path()) @@ -428,6 +439,7 @@ def set_refresh_cookies(response, encoded_refresh_token): if get_cookie_csrf_protect(): response.set_cookie(get_refresh_csrf_cookie_name(), value=_get_csrf_token(encoded_refresh_token), + max_age=_get_cookie_max_age(), secure=get_cookie_secure(), httponly=False, path='/') diff --git a/tests/test_config.py b/tests/test_config.py index cfe1cb70..0242c2fc 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -10,7 +10,7 @@ get_token_location, get_cookie_secure, get_access_cookie_name, \ get_refresh_cookie_name, get_access_cookie_path, get_refresh_cookie_path, \ get_cookie_csrf_protect, get_access_csrf_cookie_name, \ - get_refresh_csrf_cookie_name, get_csrf_header_name + get_refresh_csrf_cookie_name, get_csrf_header_name, get_session_cookie from flask_jwt_extended import JWTManager @@ -34,6 +34,7 @@ def test_default_configs(self): self.assertEqual(get_refresh_cookie_name(), 'refresh_token_cookie') self.assertEqual(get_access_cookie_path(), None) self.assertEqual(get_refresh_cookie_path(), None) + self.assertEqual(get_session_cookie(), True) self.assertEqual(get_cookie_csrf_protect(), True) self.assertEqual(get_access_csrf_cookie_name(), 'csrf_access_token') self.assertEqual(get_refresh_csrf_cookie_name(), 'csrf_refresh_token') @@ -56,6 +57,7 @@ def test_override_configs(self): self.app.config['JWT_REFRESH_COOKIE_NAME'] = 'banana2' self.app.config['JWT_ACCESS_COOKIE_PATH'] = '/banana/' self.app.config['JWT_REFRESH_COOKIE_PATH'] = '/banana2/' + self.app.config['JWT_SESSION_COOKIE'] = False self.app.config['JWT_COOKIE_CSRF_PROTECT'] = False self.app.config['JWT_ACCESS_CSRF_COOKIE_NAME'] = 'banana1a' self.app.config['JWT_REFRESH_CSRF_COOKIE_NAME'] = 'banana2a' @@ -78,6 +80,7 @@ def test_override_configs(self): self.assertEqual(get_refresh_cookie_name(), 'banana2') self.assertEqual(get_access_cookie_path(), '/banana/') self.assertEqual(get_refresh_cookie_path(), '/banana2/') + self.assertEqual(get_session_cookie(), False) self.assertEqual(get_cookie_csrf_protect(), False) self.assertEqual(get_access_csrf_cookie_name(), 'banana1a') self.assertEqual(get_refresh_csrf_cookie_name(), 'banana2a')