Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

flask_jwt_extended is throwing an error decoding my JWT. How can I capture it?

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

like image 727
NotMe Avatar asked Apr 30 '19 09:04

NotMe


People also ask

How do you decrypt a JWT token in Python?

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.

How do you expire a JWT token on logout flask?

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).

What is @jwt_required?

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.


1 Answers

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.

like image 145
NotMe Avatar answered Sep 22 '22 14:09

NotMe