Skip to content
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
5 changes: 3 additions & 2 deletions flask_jwt_extended/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
from .utils import (
create_refresh_token, create_access_token, get_jwt_identity,
get_jwt_claims, set_access_cookies, set_refresh_cookies,
unset_jwt_cookies, get_raw_jwt, get_current_user, current_user,
get_jti, decode_token, get_csrf_token
unset_jwt_cookies, unset_access_cookies, unset_refresh_cookies,
get_raw_jwt, get_current_user, current_user, get_jti, decode_token,
get_csrf_token
)

__version__ = '3.8.1'
54 changes: 42 additions & 12 deletions flask_jwt_extended/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,18 +281,23 @@ def unset_jwt_cookies(response):

:param response: The Flask response object to delete the JWT cookies in.
"""
unset_access_cookies(response)
unset_refresh_cookies(response)


def unset_access_cookies(response):
"""
takes a flask response object, and configures it to unset (delete) the
access token from the response cookies. if `jwt_csrf_in_cookies`
(see :ref:`configuration options`) is `true`, this will also remove the
access csrf double submit value from the response cookies as well.

:param response: the flask response object to delete the jwt cookies in.
"""
if not config.jwt_in_cookies:
raise RuntimeWarning("unset_refresh_cookies() called without "
"'JWT_TOKEN_LOCATION' configured to use cookies")

response.set_cookie(config.refresh_cookie_name,
value='',
expires=0,
secure=config.cookie_secure,
httponly=True,
domain=config.cookie_domain,
path=config.refresh_cookie_path,
samesite=config.cookie_samesite)
response.set_cookie(config.access_cookie_name,
value='',
expires=0,
Expand All @@ -303,19 +308,44 @@ def unset_jwt_cookies(response):
samesite=config.cookie_samesite)

if config.csrf_protect and config.csrf_in_cookies:
response.set_cookie(config.refresh_csrf_cookie_name,
response.set_cookie(config.access_csrf_cookie_name,
value='',
expires=0,
secure=config.cookie_secure,
httponly=False,
domain=config.cookie_domain,
path=config.refresh_csrf_cookie_path,
path=config.access_csrf_cookie_path,
samesite=config.cookie_samesite)
response.set_cookie(config.access_csrf_cookie_name,


def unset_refresh_cookies(response):
"""
takes a flask response object, and configures it to unset (delete) the
refresh token from the response cookies. if `jwt_csrf_in_cookies`
(see :ref:`configuration options`) is `true`, this will also remove the
refresh csrf double submit value from the response cookies as well.

:param response: the flask response object to delete the jwt cookies in.
"""
if not config.jwt_in_cookies:
raise RuntimeWarning("unset_refresh_cookies() called without "
"'JWT_TOKEN_LOCATION' configured to use cookies")

response.set_cookie(config.refresh_cookie_name,
value='',
expires=0,
secure=config.cookie_secure,
httponly=True,
domain=config.cookie_domain,
path=config.refresh_cookie_path,
samesite=config.cookie_samesite)

if config.csrf_protect and config.csrf_in_cookies:
response.set_cookie(config.refresh_csrf_cookie_name,
value='',
expires=0,
secure=config.cookie_secure,
httponly=False,
domain=config.cookie_domain,
path=config.access_csrf_cookie_path,
path=config.refresh_csrf_cookie_path,
samesite=config.cookie_samesite)
36 changes: 31 additions & 5 deletions tests/test_cookies.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from flask_jwt_extended import (
jwt_required, JWTManager, jwt_refresh_token_required, create_access_token,
create_refresh_token, set_access_cookies, set_refresh_cookies,
unset_jwt_cookies, jwt_optional
unset_jwt_cookies, unset_access_cookies, unset_refresh_cookies, jwt_optional
)

def _get_cookie_from_response(response, cookie_name):
Expand Down Expand Up @@ -46,6 +46,18 @@ def delete_tokens():
unset_jwt_cookies(resp)
return resp

@app.route('/delete_access_tokens', methods=['GET'])
def delete_access_tokens():
resp = jsonify(access_revoked=True)
unset_access_cookies(resp)
return resp

@app.route('/delete_refresh_tokens', methods=['GET'])
def delete_refresh_tokens():
resp = jsonify(refresh_revoked=True)
unset_refresh_cookies(resp)
return resp

@app.route('/protected', methods=['GET'])
@jwt_required
def protected():
Expand Down Expand Up @@ -75,12 +87,12 @@ def optional_post_protected():


@pytest.mark.parametrize("options", [
('/refresh_token', 'refresh_token_cookie', '/refresh_protected'),
('/access_token', 'access_token_cookie', '/protected')
('/refresh_token', 'refresh_token_cookie', '/refresh_protected', '/delete_refresh_tokens'),
('/access_token', 'access_token_cookie', '/protected', '/delete_access_tokens')
])
def test_jwt_refresh_required_with_cookies(app, options):
test_client = app.test_client()
auth_url, cookie_name, protected_url = options
auth_url, cookie_name, protected_url, delete_url = options

# Test without cookies
response = test_client.get(protected_url)
Expand All @@ -94,7 +106,17 @@ def test_jwt_refresh_required_with_cookies(app, options):
assert response.get_json() == {'foo': 'bar'}

# Test after issuing a 'logout' to delete the cookies
test_client.get('/delete_tokens')
test_client.get(delete_url)
response = test_client.get(protected_url)
assert response.status_code == 401
assert response.get_json() == {'msg': 'Missing cookie "{}"'.format(cookie_name)}

# log back in once more to test that clearing all tokens works
test_client.get(auth_url)
response = test_client.get(protected_url)
assert response.status_code == 200

test_client.get("/delete_tokens")
response = test_client.get(protected_url)
assert response.status_code == 401
assert response.get_json() == {'msg': 'Missing cookie "{}"'.format(cookie_name)}
Expand Down Expand Up @@ -217,6 +239,10 @@ def test_setting_cookies_wihout_cookies_enabled(app):
assert response.status_code == 500
response = test_client.get('/delete_tokens')
assert response.status_code == 500
response = test_client.get('/delete_access_tokens')
assert response.status_code == 500
response = test_client.get('/delete_refresh_tokens')
assert response.status_code == 500


def test_default_cookie_options(app):
Expand Down