I'm having issues trying to capture a malformed JWT error in my app.
I'm using flask_jwt_extended and when I send a manually created JWT. I get this error message:
Error on request:
Traceback (most recent call last):
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/jwt/api_jws.py", line 180, in _load
signing_input, crypto_segment = jwt.rsplit(b'.', 1)
ValueError: not enough values to unpack (expected 2, got 1)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/flask_restful/__init__.py", line 266, in error_router
return self.handle_error(e)
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/flask/app.py", line 1813, in full_dispatch_request
rv = self.dispatch_request()
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/flask/app.py", line 1799, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/flask_restful/__init__.py", line 458, in wrapper
resp = resource(*args, **kwargs)
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/flask/views.py", line 88, in view
return self.dispatch_request(*args, **kwargs)
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/flask_restful/__init__.py", line 573, in dispatch_request
resp = meth(*args, **kwargs)
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/flask_jwt_extended/view_decorators.py", line 103, in wrapper
verify_jwt_in_request()
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/flask_jwt_extended/view_decorators.py", line 32, in verify_jwt_in_request
jwt_data = _decode_jwt_from_request(request_type='access')
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/flask_jwt_extended/view_decorators.py", line 267, in _decode_jwt_from_request
decoded_token = decode_token(encoded_token, csrf_token)
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/flask_jwt_extended/utils.py", line 80, in decode_token
encoded_token, verify=False, algorithms=config.algorithm
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/jwt/api_jwt.py", line 84, in decode
payload, _, _, _ = self._load(jwt)
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/jwt/api_jws.py", line 183, in _load
raise DecodeError('Not enough segments')
jwt.exceptions.DecodeError: Not enough segments
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/werkzeug/serving.py", line 302, in run_wsgi
execute(self.server.app)
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/werkzeug/serving.py", line 290, in execute
application_iter = app(environ, start_response)
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/flask/app.py", line 2309, in __call__
return self.wsgi_app(environ, start_response)
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/flask/app.py", line 2295, in wsgi_app
response = self.handle_exception(e)
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/flask_restful/__init__.py", line 269, in error_router
return original_handler(e)
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/flask/app.py", line 1741, in handle_exception
reraise(exc_type, exc_value, tb)
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/flask/_compat.py", line 34, in reraise
raise value.with_traceback(tb)
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/flask/app.py", line 2292, in wsgi_app
response = self.full_dispatch_request()
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/flask/app.py", line 1815, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/flask_restful/__init__.py", line 269, in error_router
return original_handler(e)
File "/Users/desmondlim/.virtualenvs/rest-api/lib/python3.7/site-packages/flask/app.py", line 1719, in handle_user_exception
return handler(e)
TypeError: invalid_token() takes 0 positional arguments but 1 was given
My token is just this
AUTH_T wrong-token
Which should fail.
I've created a project with the same error:
app.py
import resource as testing
from flask import Flask, jsonify
from flask_restful import Api
from flask_jwt_extended import JWTManager
from jwt import InvalidSignatureError
app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = 'secret-key'
app.config['JWT_HEADER_TYPE'] = 'AUTH_T'
app.config['JWT_BLACKLIST_ENABLED'] = True
app.config['JWT_BLACKLIST_TOKEN_CHECKS'] = ['access', 'refresh']
app.config['PROPAGATE_EXCEPTIONS'] = True
app.config['DEBUG'] = False
api = Api(app)
jwt = JWTManager(app)
@jwt.invalid_token_loader
def invalid_token():
return jsonify({
'message': 'Invalid token.',
'error': 'invalid_token'
}), 401
@jwt.revoked_token_loader
def revoked_token():
return jsonify({
'message': 'Token is revoked.',
'error': 'revoked_token'
}), 401
@app.errorhandler(InvalidSignatureError)
def invalid_signature():
return jsonify({
'message': 'Invalid signature token.',
'error': 'wrong_token'
}), 401
api.add_resource(testing.Testing, '/test')
if __name__ == '__main__':
app.run(port=5000, debug=False)
resource.py
from flask_jwt_extended import jwt_required
from flask_restful import Resource
class Testing(Resource):
@jwt_required
def get(self):
return {'message': 'okay'}, 200
This is very odd. From what I've read, what I've done should have solved the problem but it seems that the issue is still around. Anyone have any ideas on a fix? If anyone runs this does it work?
Desmond
The library PyJWT has an option to decode a JWT without verification: Without this option, the decode function does not only decode the token but also verifies the signature and you would have to provide the matching key. And that's of course the recommended way.
Okay, so normally the client side stores the token somewhere while using JWT authentication, and attaches it to any request that needs authentication. Thus, the first thing to do when logging out is simply delete the token that you saved on the client (i.e. local storage browser).
get_jwt_identity() → Any[source] In a protected endpoint, this will return the identity of the JWT that is accessing the endpoint. If no JWT is present due to jwt_required(optional=True) , None is returned. Returns. The identity of the JWT in the current request.
Answering my own question so that I don't loose this knowledge and to help any new comers.
It seems that Flask-JWT-Extended doesn't handle the malformed tokens for their @jwt_required and @jwt_refresh_token_required decorators, so we have to write our own.
These are my codes for the decorators:
def jwt_needed(func):
@wraps(func)
def decorator(*args, **kwargs):
try:
verify_jwt_in_request()
except (ValueError, DecodeError, TypeError, WrongTokenError):
return {'error': 'access token error'}, 401
return func(*args, **kwargs)
return decorator
def jwt_refresh_token_needed(func):
@wraps(func)
def decorator(*args, **kwargs):
try:
verify_jwt_refresh_token_in_request()
except (ValueError, DecodeError, TypeError, WrongTokenError):
return {'error': 'refresh token error'}, 401
return func(*args, **kwargs)
return decorator
These 2 decorators handles the malformed token error (ValueError, DecodeError, TypeError) and also the WrongTokenError (which means that you are passing an access token when a refresh token is required). verify_jwt_refresh_token_in_request is a function of Flask-JWT-Extended itself.
Further to this. If you need admin access to a function, you can also create a decorator like this:
def admin_needed(func):
@wraps(func)
def decorator(*args, **kwargs):
try:
verify_jwt_in_request()
except (ValueError, DecodeError, TypeError, WrongTokenError):
return {'error': 'access token error'}, 401
claims = get_jwt_claims()
if claims['auth'] == 'ADMIN':
return func(*args, **kwargs)
else:
return {'error': 'admin required'}, 401
return decorator
This allows for checking of the token and the checking of the claims, all in 1 step.
Hope this would help someone.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With