Skip to content

Commit cd5783e

Browse files
authored
Merge pull request #23 from portrain/fix/jwt-access-revoke
Fix/jwt access revoke
2 parents 8a650a7 + 005186b commit cd5783e

File tree

4 files changed

+43
-12
lines changed

4 files changed

+43
-12
lines changed

docs/options.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,6 @@ The available options are:
4848
``JWT_BLACKLIST_ENABLED`` Enable/disable token blacklisting and revoking. Defaults to ``False``
4949
``JWT_BLACKLIST_STORE`` Where to save created and revoked tokens. `See here
5050
<http://pythonhosted.org/simplekv/>`_ for options.
51-
``JWT_BLACKLIST_CHECKS`` What token types to check against the blacklist. Options are
51+
``JWT_BLACKLIST_TOKEN_CHECKS`` What token types to check against the blacklist. Options are
5252
``'refresh'`` or ``'all'``. Defaults to ``'refresh'``. Only used if blacklisting is enabled.
5353
================================= =========================================

examples/blacklist.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,19 @@ def refresh():
5656
return jsonify(ret), 200
5757

5858

59-
# Endpoint for revoking a token when logging out
60-
@app.route('/logout', method=['POST'])
59+
# Endpoint for revoking an access token when logging out.
60+
# Please make sure JWT_BLACKLIST_TOKEN_CHECKS is set to 'all'
61+
@app.route('/logout', methods=['POST'])
6162
@jwt_required
6263
def logout():
6364
jwt = get_raw_jwt()
6465
jti = jwt['jti']
65-
revoke_token(jti)
66+
try:
67+
revoke_token(jti)
68+
except KeyError:
69+
return jsonify({
70+
'msg': 'Requires access tokens to be blacklisted'
71+
}), 500
6672
return jsonify({"msg": "Successfully logged out"}), 200
6773

6874

flask_jwt_extended/utils.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ def get_jwt_identity():
3030
Returns the identity of the JWT in this context. If no JWT is present,
3131
None is returned.
3232
"""
33-
return getattr(ctx_stack.top, 'jwt_identity', None)
33+
return get_raw_jwt().get('identity', None)
3434

3535

3636
def get_jwt_claims():
3737
"""
3838
Returns the dictionary of custom use claims in this JWT. If no custom user
3939
claims are present, an empty dict is returned
4040
"""
41-
return getattr(ctx_stack.top, 'jwt_user_claims', {})
41+
return get_raw_jwt().get('user_claims', {})
4242

4343

4444
def get_raw_jwt():
@@ -241,8 +241,6 @@ def wrapper(*args, **kwargs):
241241

242242
# Save the jwt in the context so that it can be accessed later by
243243
# the various endpoints that is using this decorator
244-
ctx_stack.top.jwt_identity = jwt_data['identity']
245-
ctx_stack.top.jwt_user_claims = jwt_data['user_claims']
246244
ctx_stack.top.jwt = jwt_data
247245
return fn(*args, **kwargs)
248246
return wrapper
@@ -277,8 +275,6 @@ def wrapper(*args, **kwargs):
277275

278276
# Save the jwt in the context so that it can be accessed later by
279277
# the various endpoints that is using this decorator
280-
ctx_stack.top.jwt_identity = jwt_data['identity']
281-
ctx_stack.top.jwt_user_claims = jwt_data['user_claims']
282278
ctx_stack.top.jwt = jwt_data
283279
return fn(*args, **kwargs)
284280
return wrapper
@@ -306,7 +302,6 @@ def wrapper(*args, **kwargs):
306302

307303
# Save the jwt in the context so that it can be accessed later by
308304
# the various endpoints that is using this decorator
309-
ctx_stack.top.jwt_identity = jwt_data['identity']
310305
ctx_stack.top.jwt = jwt_data
311306
return fn(*args, **kwargs)
312307
return wrapper

tests/test_blacklist.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from flask import Flask, jsonify, request
88
from flask_jwt_extended.blacklist import _get_token_ttl, get_stored_token
99
from flask_jwt_extended.utils import _encode_refresh_token, _decode_jwt, \
10-
fresh_jwt_required, get_jwt_identity
10+
fresh_jwt_required, get_jwt_identity, get_raw_jwt
1111

1212
from flask_jwt_extended import JWTManager, create_access_token, \
1313
get_all_stored_tokens, get_stored_tokens, revoke_token, unrevoke_token, \
@@ -71,6 +71,14 @@ def refresh():
7171
ret = {'access_token': create_access_token(username, fresh=False)}
7272
return jsonify(ret), 200
7373

74+
@self.app.route('/auth/logout', methods=['POST'])
75+
@jwt_required
76+
def logout():
77+
jti = get_raw_jwt()['jti']
78+
revoke_token(jti)
79+
ret = {"msg": "Successfully logged out"}
80+
return jsonify(ret), 200
81+
7482
@self.app.route('/protected', methods=['POST'])
7583
@jwt_required
7684
def protected():
@@ -284,6 +292,28 @@ def test_revoked_refresh_token(self):
284292
self.assertEqual(status, 200)
285293
self.assertIn('access_token', data)
286294

295+
def test_login_logout(self):
296+
# Check access and refresh tokens
297+
self.app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = 'all'
298+
299+
# Login
300+
access_token, refresh_token = self._login('test12345')
301+
302+
# Verify we can access the protected endpoint
303+
status, data = self._jwt_post('/protected', access_token)
304+
self.assertEqual(status, 200)
305+
self.assertEqual(data, {'hello': 'world'})
306+
307+
# Logout
308+
status, data = self._jwt_post('/auth/logout', access_token)
309+
self.assertEqual(status, 200)
310+
self.assertEqual(data, {'msg': 'Successfully logged out'})
311+
312+
# Verify that we cannot access the protected endpoint anymore
313+
status, data = self._jwt_post('/protected', access_token)
314+
self.assertEqual(status, 401)
315+
self.assertEqual(data, {'msg': 'Token has been revoked'})
316+
287317
def test_bad_blacklist_settings(self):
288318
app = Flask(__name__)
289319
app.testing = True # Propagate exceptions

0 commit comments

Comments
 (0)