From 9fc12f676931e0bf509e119c76efbec38f30f4af Mon Sep 17 00:00:00 2001 From: Andreas Moll Date: Fri, 13 Jan 2017 13:50:19 +1100 Subject: [PATCH 1/5] Fix typo in blacklist example --- examples/blacklist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/blacklist.py b/examples/blacklist.py index a8eaa43a..44a70ac6 100644 --- a/examples/blacklist.py +++ b/examples/blacklist.py @@ -57,7 +57,7 @@ def refresh(): # Endpoint for revoking a token when logging out -@app.route('/logout', method=['POST']) +@app.route('/logout', methods=['POST']) @jwt_required def logout(): jwt = get_raw_jwt() From 0009633028c612d6fdf96493828f40a1c77c27b2 Mon Sep 17 00:00:00 2001 From: Andreas Moll Date: Fri, 13 Jan 2017 13:54:04 +1100 Subject: [PATCH 2/5] Improve logout method in blacklist example --- examples/blacklist.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/blacklist.py b/examples/blacklist.py index 44a70ac6..f67a3912 100644 --- a/examples/blacklist.py +++ b/examples/blacklist.py @@ -56,13 +56,19 @@ def refresh(): return jsonify(ret), 200 -# Endpoint for revoking a token when logging out +# Endpoint for revoking an access token when logging out. +# Please make sure JWT_BLACKLIST_TOKEN_CHECKS is set to 'all' @app.route('/logout', methods=['POST']) @jwt_required def logout(): jwt = get_raw_jwt() jti = jwt['jti'] - revoke_token(jti) + try: + revoke_token(jti) + except KeyError: + return jsonify({ + 'msg': 'Requires access tokens to be blacklisted' + }), 500 return jsonify({"msg": "Successfully logged out"}), 200 From 9aa6bd30ff77fb85e64a5a73ce50c108b16be45d Mon Sep 17 00:00:00 2001 From: Andreas Moll Date: Fri, 13 Jan 2017 13:55:12 +1100 Subject: [PATCH 3/5] Fix BLACKLIST_TOKEN_CHECKS option in doc --- docs/options.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/options.rst b/docs/options.rst index 8bf1ed6f..36f26b46 100644 --- a/docs/options.rst +++ b/docs/options.rst @@ -48,6 +48,6 @@ The available options are: ``JWT_BLACKLIST_ENABLED`` Enable/disable token blacklisting and revoking. Defaults to ``False`` ``JWT_BLACKLIST_STORE`` Where to save created and revoked tokens. `See here `_ for options. -``JWT_BLACKLIST_CHECKS`` What token types to check against the blacklist. Options are +``JWT_BLACKLIST_TOKEN_CHECKS`` What token types to check against the blacklist. Options are ``'refresh'`` or ``'all'``. Defaults to ``'refresh'``. Only used if blacklisting is enabled. ================================= ========================================= From 1a4870effd04b411342dc2b7a47a94cfadf644be Mon Sep 17 00:00:00 2001 From: Andreas Moll Date: Fri, 13 Jan 2017 13:58:12 +1100 Subject: [PATCH 4/5] Remove redundant jwt information on the context --- flask_jwt_extended/utils.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/flask_jwt_extended/utils.py b/flask_jwt_extended/utils.py index 7081d0ea..03d9fd29 100644 --- a/flask_jwt_extended/utils.py +++ b/flask_jwt_extended/utils.py @@ -30,7 +30,7 @@ def get_jwt_identity(): Returns the identity of the JWT in this context. If no JWT is present, None is returned. """ - return getattr(ctx_stack.top, 'jwt_identity', None) + return get_raw_jwt().get('identity', None) def get_jwt_claims(): @@ -38,7 +38,7 @@ def get_jwt_claims(): Returns the dictionary of custom use claims in this JWT. If no custom user claims are present, an empty dict is returned """ - return getattr(ctx_stack.top, 'jwt_user_claims', {}) + return get_raw_jwt().get('user_claims', {}) def get_raw_jwt(): @@ -241,8 +241,6 @@ def wrapper(*args, **kwargs): # Save the jwt in the context so that it can be accessed later by # the various endpoints that is using this decorator - ctx_stack.top.jwt_identity = jwt_data['identity'] - ctx_stack.top.jwt_user_claims = jwt_data['user_claims'] ctx_stack.top.jwt = jwt_data return fn(*args, **kwargs) return wrapper @@ -277,8 +275,6 @@ def wrapper(*args, **kwargs): # Save the jwt in the context so that it can be accessed later by # the various endpoints that is using this decorator - ctx_stack.top.jwt_identity = jwt_data['identity'] - ctx_stack.top.jwt_user_claims = jwt_data['user_claims'] ctx_stack.top.jwt = jwt_data return fn(*args, **kwargs) return wrapper @@ -306,7 +302,6 @@ def wrapper(*args, **kwargs): # Save the jwt in the context so that it can be accessed later by # the various endpoints that is using this decorator - ctx_stack.top.jwt_identity = jwt_data['identity'] ctx_stack.top.jwt = jwt_data return fn(*args, **kwargs) return wrapper From 005186b27043d0c44d57c8774a1d9ddd5876d235 Mon Sep 17 00:00:00 2001 From: Andreas Moll Date: Fri, 13 Jan 2017 14:02:34 +1100 Subject: [PATCH 5/5] Add test for logout function --- tests/test_blacklist.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/tests/test_blacklist.py b/tests/test_blacklist.py index fa4871e7..afc8e7ec 100644 --- a/tests/test_blacklist.py +++ b/tests/test_blacklist.py @@ -7,7 +7,7 @@ from flask import Flask, jsonify, request from flask_jwt_extended.blacklist import _get_token_ttl, get_stored_token from flask_jwt_extended.utils import _encode_refresh_token, _decode_jwt, \ - fresh_jwt_required, get_jwt_identity + fresh_jwt_required, get_jwt_identity, get_raw_jwt from flask_jwt_extended import JWTManager, create_access_token, \ get_all_stored_tokens, get_stored_tokens, revoke_token, unrevoke_token, \ @@ -71,6 +71,14 @@ def refresh(): ret = {'access_token': create_access_token(username, fresh=False)} return jsonify(ret), 200 + @self.app.route('/auth/logout', methods=['POST']) + @jwt_required + def logout(): + jti = get_raw_jwt()['jti'] + revoke_token(jti) + ret = {"msg": "Successfully logged out"} + return jsonify(ret), 200 + @self.app.route('/protected', methods=['POST']) @jwt_required def protected(): @@ -284,6 +292,28 @@ def test_revoked_refresh_token(self): self.assertEqual(status, 200) self.assertIn('access_token', data) + def test_login_logout(self): + # Check access and refresh tokens + self.app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = 'all' + + # Login + access_token, refresh_token = self._login('test12345') + + # Verify we can access the protected endpoint + status, data = self._jwt_post('/protected', access_token) + self.assertEqual(status, 200) + self.assertEqual(data, {'hello': 'world'}) + + # Logout + status, data = self._jwt_post('/auth/logout', access_token) + self.assertEqual(status, 200) + self.assertEqual(data, {'msg': 'Successfully logged out'}) + + # Verify that we cannot access the protected endpoint anymore + status, data = self._jwt_post('/protected', access_token) + self.assertEqual(status, 401) + self.assertEqual(data, {'msg': 'Token has been revoked'}) + def test_bad_blacklist_settings(self): app = Flask(__name__) app.testing = True # Propagate exceptions