An open source Flask extension that provides JWT support (with batteries included)!

Overview

Flask-JWT-Extended

Build Status Coverage Status PyPI version Documentation Status

Features

Flask-JWT-Extended not only adds support for using JSON Web Tokens (JWT) to Flask for protecting views, but also many helpful (and optional) features built in to make working with JSON Web Tokens easier. These include:

  • Support for adding custom claims to JSON Web Tokens
  • Custom claims validation on received tokens
  • Creating tokens from complex objects or complex object from received tokens
  • Refresh tokens
  • Token freshness and separate view decorators to only allow fresh tokens
  • Token revoking/blacklisting
  • Storing tokens in cookies and CSRF protection

Usage

View the documentation online

Changelog

You can view the changelog here. This project follows semantic versioning.

Chatting

Come chat with the community or ask questions at https://discord.gg/EJBsbFd

Local Development

We require 100% code coverage in our unit tests. You can run the tests locally with tox which will print out a code coverage report. Creating a pull request will run the tests against python 2.7, 3.5, 3.6, 3.7, 3.8 and PyPy.

$ tox

We also require features to be well documented. After installing the requirements, you can generate a local copy of documentation by going to the docs directory and running:

$ make clean && make html
Comments
  • ImportError: cannot import name 'ExpiredSignatureError'

    ImportError: cannot import name 'ExpiredSignatureError'

    I got a traceback from flask_jwt_extended import jwt_required File "D:\Program Files\Python\Python36\lib\site-packages\flask_jwt_extended_init_.py", line 1, in from .jwt_manager import JWTManager File "D:\Program Files\Python\Python36\lib\site-packages\flask_jwt_extended\jwt_manager.py", line 3, in from jwt import ExpiredSignatureError, InvalidTokenError ImportError: cannot import name 'ExpiredSignatureError'

    opened by pingisheng 19
  • API support for multiple secrets?

    API support for multiple secrets?

    We've considered using a JWT as a relatively long-lived server-to-server API key (for a handful of first and second-party servers) that specifies permitted endpoints/restrictions/etc, on which we'd use blacklisting. We've also considered various short-lived client authorization uses where we don't really need to worry about blacklisting.

    I'm not sure how much it would matter in reality, but I would be a little more comfortable using JWTs for both purposes if it was trivial to specify separate secrets for each use case. This would:

    • reduce the number (probably by at least a few orders of magnitude) of tokens floating around that could be used to attempt brute-force recovery of the secret used to sign the API-key tokens
    • enable us to rotate more heavily-used auth secrets without having to temporarily break integrations and redistribute API-keys

    One approach might be using the existing config vars when the decorators are used normally, and adding support for explicitly passing one or more secrets to the decorator. I think this could be reasonably intuitive:

    API_KEY_SECRET = os.getenv("API_KEY_SECRET")
    AUTH_SECRET = os.getenv("AUTH_SECRET")
    
    @jwt_required(API_KEY_SECRET)
    @route("/partner_api")
    def partners(self):
       ...
    
    @jwt_required(AUTH_SECRET)
    @route("/account")
    def account(self):
       ...
    

    A more robust version might be for the decorators to accept your config objects, along with a factory that'll generate a config object as you do currently (with defaults from app.config), but override any user-supplied values.

    opened by abathur 19
  • Exceptions in production not producing json

    Exceptions in production not producing json

    Flask with blueprint pattern, using Flask Restful with a Token Resource class and method decorator for jwt_required.

    '''In app.py'''
    from flask_jwt_extended import JWTManager
    from flask import Flask
    
    jwtmanager = JWTManager()
    
    def create_app():
        app = Flask(__name__)
    
    def register_extensions(app):
        jwtmanager.init_app(app)
    
    
    '''in views.py'''
    from flask_restful import Resource, Api
    from flask_jwt_extended import jwt_required
    
    api = Api()
    
    class TokenResource(Resource):
        method_decorators = [jwt_required]
    
    class HelloWorld(TokenResource):
        def get(self):
            return 'Hello, World!'
    
    api.add_resource(HelloWorld, '/hello')
    

    The issue I am having is with exceptions and it likely has little to do with this library. When I run into a common jwt_extended exception on the flask development server with debug enabled I get this which is awesome!

    {
      "msg": "Token has expired"
    }
    
    

    With Gunicorn I get this on the exact same request with the same expired token. gunicorn --reload --worker-class "gevent" "app:create_app()"

    {"message": "Internal Server Error"}
    
    

    with a traceback that the development server never showed.

    File "/usr/local/lib/python3.5/site-packages/flask/app.py", line 1639, in full_dispatch_request
    rv = self.dispatch_request()
    File "/usr/local/lib/python3.5/site-packages/flask/app.py", line 1625, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
    File "/usr/local/lib/python3.5/site-packages/flask_restful/__init__.py", line 477, in wrapper
    resp = resource(*args, **kwargs)
    File "/usr/local/lib/python3.5/site-packages/flask/views.py", line 84, in view
    return self.dispatch_request(*args, **kwargs)
    File "/usr/local/lib/python3.5/site-packages/flask_restful/__init__.py", line 587, in dispatch_request
    resp = meth(*args, **kwargs)
    File "/usr/local/lib/python3.5/site-packages/flask_jwt_extended/utils.py", line 222, in wrapper
    jwt_data = _decode_jwt_from_request(type='access')
    File "/usr/local/lib/python3.5/site-packages/flask_jwt_extended/utils.py", line 204, in _decode_jwt_from_request
    return _decode_jwt_from_headers()
    File "/usr/local/lib/python3.5/site-packages/flask_jwt_extended/utils.py", line 176, in _decode_jwt_from_headers
    return _decode_jwt(token, secret, algorithm)
    File "/usr/local/lib/python3.5/site-packages/flask_jwt_extended/utils.py", line 136, in _decode_jwt
    data = jwt.decode(token, secret, algorithm=algorithm)
    File "/usr/local/lib/python3.5/site-packages/jwt/api_jwt.py", line 75, in decode
    self._validate_claims(payload, merged_options, **kwargs)
    File "/usr/local/lib/python3.5/site-packages/jwt/api_jwt.py", line 104, in _validate_claims
    self._validate_exp(payload, now, leeway)
    File "/usr/local/lib/python3.5/site-packages/jwt/api_jwt.py", line 149, in _validate_exp
    raise ExpiredSignatureError('Signature has expired')
    jwt.exceptions.ExpiredSignatureError: Signature has expired
    

    It is raising the exception and I imagine it is calling the Signature expired callback which sends the json back out but clearly something goes wrong and it bubbles up and Flask hands out a 500 in place of what would have been a signature expired json message as the flask dev server does.

    This probably has nothing to do with this specific library at all and any hints at all your give me about which direction to go in on this would be much appreciated by me. Using this with the flask development server has been perfect but now getting something ready for production I am encountering this persistent issue. My regular views return json just fine with the production setup but these exceptions don't. I was hoping maybe somebody had been running this in production and would be able to give me a hint on where I went wrong here. Thanks!

    opened by constemi 17
  • JWT token and concurrent sessions

    JWT token and concurrent sessions

    Hello,

    I am seeing some weirdness around JWT token usage. If the same user credentials are used to generate tokens from different locations, one of the user gets a 500 internal server error.

    I see the below entries in the logs. Wondering if this is a known behavior? Is there anyway to mitigate it?

    File "/var/task/flask_jwt_extended/config.py", line 58, in jwt_in_cookies return 'cookies' in self.token_location File "/var/task/flask_jwt_extended/config.py", line 44, in token_location locations = current_app.config['JWT_TOKEN_LOCATION'] KeyError: 'JWT_TOKEN_LOCATION'

    opened by ctippur 15
  • Token Sidejacking prevention

    Token Sidejacking prevention

    Hi mate,

    I've been reading this attack vector from the OWASP: https://www.owasp.org/index.php/JSON_Web_Token_(JWT)_Cheat_Sheet_for_Java#Token_sidejacking

    And I was wondering what you reckon about the option of adding "user context" and then validating it. It would be trivial to add this data as you describe here: https://flask-jwt-extended.readthedocs.io/en/latest/add_custom_data_claims.html, but I'm not sure how easy would be to then add that check to the JWT validation process as well, as opposed to having to do it manually for every request an API receives if there is no native support from this module.

    Cheers!

    opened by adonis28850 15
  • jwt decorator

    jwt decorator

    Hi, I found an issue with jwt_required decorator, I don't understand why works when I used like:

    @custom_api.route('/resellers/<token>/registrations', methods=['GET'])
    @jwt_required
    def get_resellers(token):
      ...
    

    but NOT when:

    I'm using https://flask-restless.readthedocs.io/en/stable/ where I can use methods as preprocessor

        @classmethod
        @jwt_required
        def get_many_preprocessor(cls, search_params=None, **kw):
            print "Here not work"
    

    This worked me with flask-jwt, what could be?

    opened by laranicolas 15
  • Invalid JWT errors not thrown for decorated optional routes

    Invalid JWT errors not thrown for decorated optional routes

    I'm migrating from flask-jwt and make extensive use of decorated required and optional routes. My tests also have extensive coverage for invalid JWT cases. I've confirmed that invalid JWTs are handled correctly for jwt_required() routes, but not fully for jwt_required(optional=True) routes. Note that I use "JWT" as my prefix instead of "Bearer". I've also seen that flask-jwt-extended test cases do not cover these types of cases for optional routes.

    Specifically these routes are allowed access for these invalid tokens. It is obviously important that any type of error for a submitted token (in any location) is rejected even when it is optional.

    • "JWT "
    • "JWT xxxx"
    • "JWT xxxx xxxx"
    • "JXX"
    opened by dmulter 14
  • Is there a way to revoke both refresh token and access token when logout?

    Is there a way to revoke both refresh token and access token when logout?

    I create both refresh token and access token when login. However, when logout, those tokens should be revoked at the same time, without affecting other tokens owned by the user. I look at the doc. Like:

    # Endpoint for revoking the current users access token
    @app.route('/logout', methods=['POST'])
    @jwt_required
    def logout():
        try:
            _revoke_current_token()
        except KeyError:
            return jsonify({
                'msg': 'Access token not found in the blacklist store'
            }), 500
        return jsonify({"msg": "Successfully logged out"}), 200
    
    
    # Endpoint for revoking the current users refresh token
    @app.route('/logout2', methods=['POST'])
    @jwt_refresh_token_required
    def logout2():
        try:
            _revoke_current_token()
        except KeyError:
            return jsonify({
                'msg': 'Refresh token not found in the blacklist store'
            }), 500
        return jsonify({"msg": "Successfully logged out"}), 200
    

    Is there a way to revoke both?

    opened by alexcc4 14
  • get_jwt_identity() returning None

    get_jwt_identity() returning None

    I'm using Flask-JWT-Extended to protect my Flask API. After the Login, in the protected route (add-user), I call get_jwt_identity(), but it's returning None, so I'm not able to get the identity.

    @flask_app.route('/<api_version>/login', methods=['POST'])
    def login(api_version):
        print(f'login', request.form)
        response = None
        try:
            username = request.form['username']
            password = request.form['password']
    
            if not username:
                return jsonify({"msg": "Missing username parameter"}), 400
            if not password:
                return jsonify({"msg": "Missing password parameter"}), 400
    
            user = User.get_with_password(username, password)
    
            if (not user):
                e1 = Unauthorized(
                    'Invalid username or password.  Please try again.')
                e1.status = 401
                raise e1
    
            """ flask_login.login_user(user, remember=True) """
            access_token = create_access_token(identity=username)
            response = json.dumps({"token": access_token}, cls=CustomJSONEncoder)
        except Exception as e:
            errMsg = f'Error Logging in user {username if username else ""}: {e}'
            status = e.status if hasattr(e, 'status') else 500
            print(f'{errMsg}')
            traceback.print_exc()
            return Response(
                json.dumps({"message": errMsg, "status": status, "stack": traceback.format_exc() }), status=status, mimetype='application/json')
            
    
        resp = Response(response, status=200, mimetype='application/json')
        return resp
    
    @flask_app.route('/<api_version>/add-user', methods=['POST'])
    @jwt_required
    def add_user(api_version):
        print(f'add-user', request)
        response = None
        username = None
        password = None
        allow_admin = None
        try:
            data = request.get_json()
            print(f'add-user data', data)
    
            if 'username' in data:
                username = data['username']
            else:
                return jsonify({"msg": "Missing username parameter"}), 400
            if 'password' in data:
                password = data['password']
            else:
                return jsonify({"msg": "Missing password parameter"}), 400
            if 'allow_admin' in data:
                allow_admin = data['allow_admin']
    
            """ user = User.get_with_password(username, password)"""
            user = get_jwt_identity()
            print('user',user)
    
            if (not user):
                e1 = Unauthorized(
                    'Invalid username or password.  Please try again.')
                e1.status = 401
                raise e1
    
            response = json.dumps({"user": user}, cls=CustomJSONEncoder)
        except Exception as e:
            errMsg = f'Error Adding User {username}: {e}'
            status = e.status if hasattr(e, 'status') else 500
            print(f'{errMsg}')
            traceback.print_exc()
            return Response(
                json.dumps({"message": errMsg, "status": status, "stack": traceback.format_exc() }), status=status, mimetype='application/json')
            
    
        resp = Response(response, status=200, mimetype='application/json')
        return resp
    

    User.py

    class User():
        @classmethod
        def get_with_password(cls, username, password):
            print(f'User get_with_password {username} with password')
            user_db = account.get_account(username)
            print(f'User returned from DB: {user_db}')
    
            user = User()
            if not user_db or not len(user_db) or (not 'password' in user_db):
                return None
            user.username = username
            user.id = username
    
            if bcrypt.check_password_hash(user_db['password'], password):
    
                user.role = user_db['role']
                #user.is_authenticated = True
                print(
                    f'loginUser returning {vars(user)} ')
                return user
            return None
    
    
    opened by jcald1 13
  • Suggestion for a REST Debug mode? (integration with flask-restplus)

    Suggestion for a REST Debug mode? (integration with flask-restplus)

    Apologies if this is only tangentially a jwt issue:

    I am trying to figure out a simple way to enable a "debug mode" (conditional to FLASK_DEBUG) where @jwt_required requests succeed, regardless of the presence of an authorization header. That would be especially useful to be able to test using flask-restplus' automatically generated API docs and request forms.

    Ideally, I would simply add a custom jwt.claims_verification_loader when in debug mode, but that callback does not seem to be called at all, if the header is not set altogether.

    Is there a (clean) way to intercept the verification process higher up? Any other suggestions on the best way to implement a debug mode that will play nice with flask-restplus?

    opened by zedrdave 13
  • Integration with flask-restful & error handling

    Integration with flask-restful & error handling

    Similar to #86, errors like SignatureExpiry are returning 500 rather than 4xx errors.

    However I am using flask-restful which does not have an error_handler, so the solutions in the referenced issue do not apply.

    Flask-restful is briefly mentioned on the releases page here, but setting application.config['PROPAGATE_EXCEPTIONS'] = True didn't have any effect for me.

    Any insight into how I can correctly deal with error handling using flask-restful?

    opened by ecatkins 13
  • Flask-JWT-extended 4.4.4 is not compatible with PyJWT >= 2.6.0

    Flask-JWT-extended 4.4.4 is not compatible with PyJWT >= 2.6.0

    After upgrading to PyJWT 2.6.0 (to fix https://github.virtualitics.com/advisories/GHSA-ffqj-6fqr-9h24), the following error occurs when using create_access_token.

    File "/opt/app-root/lib/python3.8/site-packages/flask_jwt_extended/utils.py", line 172, in create_access_token
    
        return jwt_manager._create_access_token(identity, fresh, expires_delta, user_claims,
    
      File "/opt/app-root/lib/python3.8/site-packages/flask_jwt_extended/jwt_manager.py", line 511, in _create_access_token
    
        access_token = encode_access_token(
    
      File "/opt/app-root/lib/python3.8/site-packages/flask_jwt_extended/tokens.py", line 76, in encode_access_token
    
        return _encode_jwt(token_data, expires_delta, secret, algorithm,
    
      File "/opt/app-root/lib/python3.8/site-packages/flask_jwt_extended/tokens.py", line 29, in _encode_jwt
    
        encoded_token = jwt.encode(token_data, secret, algorithm,
    
    AttributeError: 'str' object has no attribute 'decode'
    
    opened by justinvirtualitics 1
  • Feature Request: Allow selective disabling of blocklist check

    Feature Request: Allow selective disabling of blocklist check

    It would be helpful to be able to provide an argument to the @jwt_required() decorator that would disable the blocklist verification for a given route.

    This (or another workaround) is needed to be fully compliant with RFC 7009 - OAuth 2.0 Token Revocation. Specifically, the RFC states, "The authorization server responds with HTTP status code 200 if the token has been revoked successfully or if the client submitted an invalid token.".

    The RFC also recommends (via should language), that if the auth server supports access token revocation, then it should revoke any access tokens associated with a refresh token when it receives a refresh token revocation request. The simplest way to achieve this is for the server to store the access and refresh tokens upon user login.

    Such a schema means that the blocklist becomes more of an allow-list, where a token is considered revoked if it does not appear in the list or table of current access tokens. This approach is mentioned in the jwt-extended documentation as the third option for revoking both access and refresh tokens.

    @token_in_blocklist_loader
    def is_denied(header, payload):
        return payload['jti'] not in active_tokens
    
    # ...
    
    @jwt_required(verify_type=False)
    def logout():
        # look for an entry in active_tokens. If it exists, delete it.
        # either way, return a 200
       token_entry = active_tokens.find_by_token(get_jwt())
       if token_entry:
           token_entry.remove()
        return {'message': 'Logout successful'}
    

    The problem is that the method logic will never run if an invalid token is sent since jwt-extended will run the revocation check, then nvoke the appropriate error handler if the token isn't recognized. Additionally, valid tokens that were previously revoked will also result in an error response (for instance if the user logs out twice in a row using the same access/refresh token, the second attempt will produce an error).

    At the same time, an error should be thrown if no token is provided (and get_jwt() doesn't function without @jwt_required()).

    The simplest solution would be to optionally bypass the revocation check when adding the decorator. Something like @jwt_required(verify_type=False, skip_revocation_check=True).

    There are other workarounds, such as not using the jwt_required decorator on the logout/revoke route (and instead doing manual validation of the token), or looking at the request object in the blocklist loader, and if it contains a specific URL, always returning False, but both of those are less than ideal.

    opened by BrianHVB 2
  • Original error messages lost

    Original error messages lost

    All error handler callbacks should include the original error message in the parameters. E.g, the _expired_token_callback takes e.jwt_header, e.jwt_data as parameters, thus the original error message is lost when setting the expired_token_loader. Either the entire error should be set as parameter, I.e. _expired_token_callback(e: Exception) or alternatively you could add the error message as a string _expired_token_callback(e.jwt_header, e.jwt_data, str(e)). The same applies for all error handlers which do not include the error message.

    opened by JustElectron 2
  • @jwt_required does not work with the OPTIONS method

    @jwt_required does not work with the OPTIONS method

    Hello,

    Thanks for the great work !

    With the OPTIONS HTTP method, a confusing error message is raised, stating that @jwt_required() was not called, although it was called:

    "You must call @jwt_required() or verify_jwt_in_request() "

    Here is a way to reproduce the issue :

    from flask import Flask, jsonify
    from flask_jwt_extended import get_jwt_identity, jwt_required, JWTManager
    
    app = Flask(__name__)
    app.config["JWT_SECRET_KEY"] = "super-secret"
    jwt = JWTManager(app)
    
    @app.route("/", methods=["GET", "OPTIONS"])
    @jwt_required(optional=True)
    def protected():
        current_user = get_jwt_identity()
        return "hello", 200
    
    
    if __name__ == "__main__":
        app.run()
    
    $ curl -X OPTIONS http://0.0.0.0:5000/ -i 
    HTTP/1.0 500 INTERNAL SERVER ERROR
    Content-Type: text/html; charset=utf-8
    Content-Length: 290
    Server: Werkzeug/2.0.2 Python/3.7.11
    Date: Wed, 13 Apr 2022 07:55:36 GMT
    
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
    <title>500 Internal Server Error</title>
    <h1>Internal Server Error</h1>
    <p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>
    
    2022-04-13 09:55:36,725] ERROR in app: Exception on / [OPTIONS]
    Traceback (most recent call last):
      File "/***/venv/lib/python3.7/site-packages/flask/app.py", line 2073, in wsgi_app
        response = self.full_dispatch_request()
      File "/***/venv/lib/python3.7/site-packages/flask/app.py", line 1518, in full_dispatch_request
        rv = self.handle_user_exception(e)
      File "/***/venv/lib/python3.7/site-packages/flask/app.py", line 1516, in full_dispatch_request
        rv = self.dispatch_request()
      File "/***/venv/lib/python3.7/site-packages/flask/app.py", line 1502, in dispatch_request
        return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
      File "/***/venv/lib/python3.7/site-packages/flask_jwt_extended/view_decorators.py", line 127, in decorator
        return current_app.ensure_sync(fn)(*args, **kwargs)
      File "test_app.py", line 11, in protected
        current_user = get_jwt_identity()
      File "/***/venv/lib/python3.7/site-packages/flask_jwt_extended/utils.py", line 58, in get_jwt_identity
        return get_jwt().get(config.identity_claim_key, None)
      File "/***/venv/lib/python3.7/site-packages/flask_jwt_extended/utils.py", line 25, in get_jwt
        "You must call `@jwt_required()` or `verify_jwt_in_request()` "
    RuntimeError: You must call `@jwt_required()` or `verify_jwt_in_request()` before using this method
    127.0.0.1 - - [13/Apr/2022 09:55:36] "OPTIONS / HTTP/1.1" 500 -
    
    opened by stackp 4
  • Is there a better way how to implement custom _decode_jwt_from_* ?

    Is there a better way how to implement custom _decode_jwt_from_* ?

    Hi, i need to parse the token from JSON that is in request.event var (SocketIO) and for that i had to implement custom _decode_jwt_from_event and wire it it like this (yes that is bunch of code cannibalized from flask-jwt-extended):

    """
    This is flask-jwt-extended version 4 implementation of custom resolver
    """
    
    
    from functools import wraps
    from flask import request, current_app
    from werkzeug.exceptions import BadRequest
    from flask_jwt_extended.config import config
    from flask_jwt_extended.view_decorators import _load_user, _decode_jwt_from_cookies, _decode_jwt_from_query_string, _decode_jwt_from_headers, _verify_token_is_fresh, _decode_jwt_from_json
    from flask_jwt_extended.exceptions import NoAuthorizationError
    from flask_jwt_extended.utils import decode_token, get_unverified_jwt_headers
    from flask_jwt_extended.internal_utils import verify_token_type, verify_token_not_blocklisted, custom_verification_for_token
    from flask import _request_ctx_stack
    
    
    def _decode_jwt_from_event(refresh):
        if not request.event:
            raise NoAuthorizationError('Invalid content-type. Must have event.')
    
        if refresh:
            token_key = config.refresh_json_key
        else:
            token_key = config.json_key
    
        try:
            encoded_token = request.event.get('args')[0].get(token_key, None)
            if not encoded_token:
                raise BadRequest()
        except BadRequest:
            raise NoAuthorizationError('Missing "{}" key in json data.'.format(token_key))
    
        return encoded_token, None
    
    
    def _decode_jwt_from_request(locations, fresh, refresh=False):
        # Figure out what locations to look for the JWT in this request
        if isinstance(locations, str):
            locations = [locations]
    
        if not locations:
            locations = config.token_location
    
        # Get the decode functions in the order specified by locations.
        # Each entry in this list is a tuple (<location>, <encoded-token-function>)
        get_encoded_token_functions = [("json", lambda: _decode_jwt_from_event(refresh))]
        for location in locations:
            if location == "cookies":
                get_encoded_token_functions.append(
                    (location, lambda: _decode_jwt_from_cookies(refresh))
                )
            elif location == "query_string":
                get_encoded_token_functions.append(
                    (location, _decode_jwt_from_query_string)
                )
            elif location == "headers":
                get_encoded_token_functions.append((location, _decode_jwt_from_headers))
            elif location == "json":
                get_encoded_token_functions.append(
                    (location, lambda: _decode_jwt_from_json(refresh))
                )
            else:
                raise RuntimeError(f"'{location}' is not a valid location")
    
        # Try to find the token from one of these locations. It only needs to exist
        # in one place to be valid (not every location).
        errors = []
        decoded_token = None
        jwt_header = None
        jwt_location = None
        for location, get_encoded_token_function in get_encoded_token_functions:
            try:
                encoded_token, csrf_token = get_encoded_token_function()
                decoded_token = decode_token(encoded_token, csrf_token)
                jwt_location = location
                jwt_header = get_unverified_jwt_headers(encoded_token)
                break
            except NoAuthorizationError as e:
                errors.append(str(e))
    
        # Do some work to make a helpful and human readable error message if no
        # token was found in any of the expected locations.
        if not decoded_token:
            if len(locations) > 1:
                err_msg = "Missing JWT in {start_locs} or {end_locs} ({details})".format(
                    start_locs=", ".join(locations[:-1]),
                    end_locs=locations[-1],
                    details="; ".join(errors),
                )
                raise NoAuthorizationError(err_msg)
            else:
                raise NoAuthorizationError(errors[0])
    
        # Additional verifications provided by this extension
        verify_token_type(decoded_token, refresh)
        if fresh:
            _verify_token_is_fresh(jwt_header, decoded_token)
        verify_token_not_blocklisted(jwt_header, decoded_token)
        custom_verification_for_token(jwt_header, decoded_token)
    
        return decoded_token, jwt_header, jwt_location
    
    
    def verify_jwt_in_request(optional=False, fresh=False, refresh=False, locations=None):
        """
        Verify that a valid JWT is present in the request, unless ``optional=True`` in
        which case no JWT is also considered valid.
    
        :param optional:
            If ``True``, do not raise an error if no JWT is present in the request.
            Defaults to ``False``.
    
        :param fresh:
            If ``True``, require a JWT marked as ``fresh`` in order to be verified.
            Defaults to ``False``.
    
        :param refresh:
            If ``True``, require a refresh JWT to be verified.
    
        :param locations:
            A location or list of locations to look for the JWT in this request, for
            example ``'headers'`` or ``['headers', 'cookies']``. Defaults to ``None``
            which indicates that JWTs will be looked for in the locations defined by the
            ``JWT_TOKEN_LOCATION`` configuration option.
        """
        if request.method in config.exempt_methods:
            return
    
        try:
            if refresh:
                jwt_data, jwt_header, jwt_location = _decode_jwt_from_request(
                    locations, fresh, refresh=True
                )
            else:
                jwt_data, jwt_header, jwt_location = _decode_jwt_from_request(
                    locations, fresh
                )
        except NoAuthorizationError:
            if not optional:
                raise
            _request_ctx_stack.top.jwt = {}
            _request_ctx_stack.top.jwt_header = {}
            _request_ctx_stack.top.jwt_user = {"loaded_user": None}
            _request_ctx_stack.top.jwt_location = None
            return
    
        # Save these at the very end so that they are only saved in the requet
        # context if the token is valid and all callbacks succeed
        _request_ctx_stack.top.jwt_user = _load_user(jwt_header, jwt_data)
        _request_ctx_stack.top.jwt_header = jwt_header
        _request_ctx_stack.top.jwt = jwt_data
        _request_ctx_stack.top.jwt_location = jwt_location
    
        return jwt_header, jwt_data
    
    
    def jwt_required(optional=False, fresh=False, refresh=False, locations=None):
        """
        A decorator to protect a Flask endpoint with JSON Web Tokens.
    
        Any route decorated with this will require a valid JWT to be present in the
        request (unless optional=True, in which case no JWT is also valid) before the
        endpoint can be called.
    
        :param optional:
            If ``True``, allow the decorated endpoint to be accessed if no JWT is present in
            the request. Defaults to ``False``.
    
        :param fresh:
            If ``True``, require a JWT marked with ``fresh`` to be able to access this
            endpoint. Defaults to ``False``.
    
        :param refresh:
            If ``True``, requires a refresh JWT to access this endpoint. If ``False``,
            requires an access JWT to access this endpoint. Defaults to ``False``.
    
        :param locations:
            A location or list of locations to look for the JWT in this request, for
            example ``'headers'`` or ``['headers', 'cookies']``. Defaults to ``None``
            which indicates that JWTs will be looked for in the locations defined by the
            ``JWT_TOKEN_LOCATION`` configuration option.
        """
    
        def wrapper(fn):
            @wraps(fn)
            def decorator(*args, **kwargs):
                verify_jwt_in_request(optional, fresh, refresh, locations)
    
                # Compatibility with flask < 2.0
                if hasattr(current_app, "ensure_sync") and callable(
                    getattr(current_app, "ensure_sync", None)
                ):
                    return current_app.ensure_sync(fn)(*args, **kwargs)
    
                return fn(*args, **kwargs)  # pragma: no cover
    
            return decorator
    
        return wrapper
    

    So my Q is, is there a better way how to do this^ (did i miss some thing?). And if there is none, will you be willing to merge a PR implementing lets say decorator custom_jwt_decoder to handle custom decoding?

    opened by Salamek 1
Releases(4.4.4)
  • 4.4.4(Aug 15, 2022)

    • Fix compatibility with flask version 2.3 (#493). Huge shout out to @jrast for taking on the bulk of this work!

    Full Changelog: https://github.com/vimalloc/flask-jwt-extended/compare/4.4.3...4.4.4

    Source code(tar.gz)
    Source code(zip)
  • 4.4.3(Jul 27, 2022)

    • Documentation improvements ( #470). Thanks @Udit107710!
    • Drop support for python 3.6 (flask no longer supports 3.6, so following suite here)
    • Add option to include current_user in jinja templates by default (#478)
    • Fix mypy type checks for current_user (#488)

    Full Changelog: https://github.com/vimalloc/flask-jwt-extended/compare/4.4.2...4.4.3

    Source code(tar.gz)
    Source code(zip)
  • 4.4.2(Jun 27, 2022)

    What's Changed

    • Fix mypy explicitly mark exported names #484 (thanks @KSmanis)
    • Fix verify_type being set to False by default in verify_jwt_in_request() #483

    Full Changelog: https://github.com/vimalloc/flask-jwt-extended/compare/4.4.1...4.4.2

    Source code(tar.gz)
    Source code(zip)
  • 4.4.1(Jun 2, 2022)

    What's Changed

    • Documentation improvements (thanks @udoyen)
    • Fix PEP 561 compatibility #480 (thanks @KSmanis)

    Full Changelog: https://github.com/vimalloc/flask-jwt-extended/compare/4.4.0...4.4.1

    Source code(tar.gz)
    Source code(zip)
  • 4.4.0(May 2, 2022)

    Overview

    • Drop support for Flask 1
      • This decision comes because flask 1 is no longer being supported upstream and a recent dependency change they released made it difficult to continue testing both versions for compatibility.
    • Add verify_type argument to view decorators to allow accepting both refresh & access tokens #460 (thanks @tgross35)
    • Adds type hinting and MyPy support
    • Documentation improvements (thanks @wjjmjh and @Udit107710)
    • Relax dependency pinning for cryptography package (#467)
    Source code(tar.gz)
    Source code(zip)
  • 4.3.1(Oct 8, 2021)

  • 4.3.0(Aug 25, 2021)

    • Allow overriding cookies domain at runtime (#446). Thanks @bejito!
    • Better compatibility with flask 1.x.x and 2.x.x (#440). Thanks @StefanVDWeide
    • Documentation updates (#443, #444). Thanks @killthekitten and @juur
    Source code(tar.gz)
    Source code(zip)
  • 4.2.3(Jul 6, 2021)

  • 4.2.2(Jul 5, 2021)

    • Added async support to jwt_required view decorator (#436). Thanks @StefanVDWeide!
    • Minor documentation improvements. Thanks @rohitsanj-jovian!
    Source code(tar.gz)
    Source code(zip)
  • 4.2.1(May 12, 2021)

  • 4.2.0(May 2, 2021)

    • Add JWT_ENCODE_NBF configuration option to allow disabling the NBF claim during token creation. Thanks @magnunleno! #416
    • Add a new get_jwt_request_location() function to determine where a token was parsed from in a request (useful for implicit token refresh with cookies). Thanks @sammck! #420
    • Fix wrong error message in edge case with current user in non-decorated route. #408
    • Fix JWT in headers followed by a comma raises IndexError #347
    • Fix edge cases where @jwt_required(optional=True) was treating a request as if there was jwt present instead of handling the InvalidHeaderError. #421
    • Add a JWT_QUERY_STRING_VALUE_PREFIX configuration option. #421
    • Update error messages to provide more helpful information to callers when they are sending in a token in an unexpected way.
    Source code(tar.gz)
    Source code(zip)
  • 4.1.0(Mar 9, 2021)

    • Allow JWT type to be things besides refresh or access (#401). Any type that is not refresh will be considered an access token. Thanks @sambonner for the PR!
    • Allow locations kwarg for jwt_required() to be a string (#394)
    • Minor documentation improvements
    Source code(tar.gz)
    Source code(zip)
  • 4.0.2(Feb 13, 2021)

  • 4.0.1(Feb 13, 2021)

  • 4.0.0(Feb 13, 2021)

    This release contains many months of work and lots of breaking changes. For full details, please see: https://flask-jwt-extended.readthedocs.io/en/stable/v4_upgrade_guide/

    Source code(tar.gz)
    Source code(zip)
  • 3.25.1(Feb 13, 2021)

    • The only change it this release is that we are setting the metadata that marks this as the last release to support python versions earlier then 3.6 (including python 2).
    Source code(tar.gz)
    Source code(zip)
  • 3.25.0(Nov 9, 2020)

    • Add JWT_ENCODE_ISSUER option
    • Require PyJWT before version 2.0.0a to prevent breaking changes. (we will update to the 2.0.0 pyjwt release once it's out of the alpha/early release).
    Source code(tar.gz)
    Source code(zip)
  • 3.24.1(Oct 24, 2019)

  • 3.24.0(Oct 4, 2019)

    • Adds the ability to add custom data to the JWT headers via the headers kwarg when making new tokens or via the jwt_manager.additional_headers_loader decorator. These headers can be accessed in your endpoints via the get_raw_jwt_header function. Thanks @iamajay for this feature! (#271)
    Source code(tar.gz)
    Source code(zip)
  • 3.23.0(Sep 10, 2019)

  • 3.22.0(Aug 28, 2019)

  • 3.21.0(Aug 3, 2019)

    • Require flask 1.0 or greater (#263)
    • Move docs to pallets-sphinx-themes (#261)
    • Add a new JWT_DECODE_ISSUER option for use with other JWT providers (#259)
    • Gracefully handle errors for malformed tokens (#246)
    Source code(tar.gz)
    Source code(zip)
  • 3.20.0(Jul 3, 2019)

  • 3.19.0(Jun 25, 2019)

  • 3.18.2(May 10, 2019)

  • 3.18.1(Apr 10, 2019)

    • Fixes an issue when using decode_token on an expired token. This issue was introduced in 3.16.0. (#234)
    • Require PyJWT 1.6.4 or newer (#238)
    Source code(tar.gz)
    Source code(zip)
  • 3.18.0(Mar 2, 2019)

    • Add the ability to dynamically set user claims via the new user_claims argument to create_access_token and create_refresh_token functions (#229). Thanks @jeanphix
    • Add ability to use other datetime libraries for the token expiration configuration options. Anything that works with datetime.datetime (such as dateutil) will now work with extension (#233). Thanks @abathur
    Source code(tar.gz)
    Source code(zip)
  • 3.17.0(Feb 1, 2019)

    • Add the ability to use an integer (seconds) for the JWT_ACCESS_TOKEN_EXPIRES and JWT_REFRESH_TOKEN_EXPIRES settings. (#226) Thanks @evangilo!
    Source code(tar.gz)
    Source code(zip)
  • 3.16.0(Jan 20, 2019)

    This release changes how the @jwt.expired_token_loader callback function works. Before this release the callback function took no arguments. Now it will take one argument which is the decoded contents of the expired token. This lets you customize the expired token callback based on the token that was received. For example:

    # Old way
    @jwt.expired_token_loader
    def old_expired_callback():
        return jsonify(foo='bar'), 401
    
    # New way
    @jwt.expired_token_loader
    def new_expired_callback(expired_token):
        if expired_token['type'] == 'access':
            return jsonify(foo='bar'), 401
        else:
            return jsonify(foo='baz'), 401
    

    The old way will still work, updating to this version will not break your software out from under you. You will however receive a deprecation warning when using that way. To fix this, simply add an addition argument to your callback function for the expired token.

    Source code(tar.gz)
    Source code(zip)
  • 3.15.0(Jan 3, 2019)

    • Adds the JWT_DECODE_LEEWAY option (#218). Thanks @otetard!
    • Adds the ability to use other data structures besides lists (such as sets, tuples, etc) as config values (#215) Thanks @illia-v!
    Source code(tar.gz)
    Source code(zip)
API with high performance to create a simple blog and Auth using OAuth2 ⛏

DogeAPI API with high performance built with FastAPI & SQLAlchemy, help to improve connection with your Backend Side to create a simple blog and Cruds

Yasser Tahiri 111 Jan 05, 2023
Python module for generating and verifying JSON Web Tokens

python-jwt Module for generating and verifying JSON Web Tokens. Note: From version 2.0.1 the namespace has changed from jwt to python_jwt, in order to

David Halls 210 Dec 24, 2022
Ready-to-use and customizable users management for FastAPI

FastAPI Users Ready-to-use and customizable users management for FastAPI Documentation: https://frankie567.github.io/fastapi-users/ Source Code: https

François Voron 2.4k Jan 04, 2023
Login System Using Django

Login System Django

Nandini Chhajed 6 Dec 12, 2021
Skit-auth - Authorization for skit.ai's platform

skit-auth This is a simple authentication library for Skit's platform. Provides

Skit 3 Jan 08, 2022
A Python library for OAuth 1.0/a, 2.0, and Ofly.

Rauth A simple Python OAuth 1.0/a, OAuth 2.0, and Ofly consumer library built on top of Requests. Features Supports OAuth 1.0/a, 2.0 and Ofly Service

litl 1.6k Dec 08, 2022
Login-python - Login system made in Python, using native libraries

login-python Sistema de login feito 100% em Python, utilizando bibliotecas nativ

Nicholas Gabriel De Matos Leal 2 Jan 28, 2022
examify-io is an online examination system that offers automatic grading , exam statistics , proctoring and programming tests , multiple user roles

examify-io is an online examination system that offers automatic grading , exam statistics , proctoring and programming tests , multiple user roles ( Examiner , Supervisor , Student )

Ameer Nasser 4 Oct 28, 2021
Authentication for Django Rest Framework

Dj-Rest-Auth Drop-in API endpoints for handling authentication securely in Django Rest Framework. Works especially well with SPAs (e.g React, Vue, Ang

Michael 1.1k Jan 03, 2023
Accounts for Django made beautifully simple

Django Userena Userena is a Django application that supplies your Django project with full account management. It's a fully customizable application t

Bread & Pepper 1.3k Sep 18, 2022
JWT Key Confusion PoC (CVE-2015-9235) Written for the Hack the Box challenge - Under Construction

JWT Key Confusion PoC (CVE-2015-9235) Written for the Hack the Box challenge - Under Construction This script performs a Java Web Token Key Confusion

Alex Fronteddu 1 Jan 13, 2022
Luca Security Concept

Luca Security Concept This is the document source of luca's security concept. Please go here for the HTML version: https://luca-app.de/securityconcept

luca 43 Oct 22, 2022
User-related REST API based on the awesome Django REST Framework

Django REST Registration User registration REST API, based on Django REST Framework. Documentation Full documentation for the project is available at

Andrzej Pragacz 399 Jan 03, 2023
Doing the OAuth dance with style using Flask, requests, and oauthlib.

Flask-Dance Doing the OAuth dance with style using Flask, requests, and oauthlib. Currently, only OAuth consumers are supported, but this project coul

David Baumgold 915 Dec 28, 2022
Ready to use and customizable Authentications and Authorisation management for FastAPI ⚡

AuthenticationX 💫 Ready-to-use and customizable Authentications and Oauth2 management for FastAPI ⚡ Source Code: https://github.com/yezz123/AuthX Doc

Yasser Tahiri 404 Dec 27, 2022
Complete Two-Factor Authentication for Django providing the easiest integration into most Django projects.

Django Two-Factor Authentication Complete Two-Factor Authentication for Django. Built on top of the one-time password framework django-otp and Django'

Bouke Haarsma 1.3k Jan 04, 2023
A JSON Web Token authentication plugin for the Django REST Framework.

Simple JWT Abstract Simple JWT is a JSON Web Token authentication plugin for the Django REST Framework. For full documentation, visit django-rest-fram

Jazzband 3.2k Dec 29, 2022
Simple extension that provides Basic, Digest and Token HTTP authentication for Flask routes

Flask-HTTPAuth Simple extension that provides Basic and Digest HTTP authentication for Flask routes. Installation The easiest way to install this is t

Miguel Grinberg 1.1k Jan 05, 2023
Django Rest Framework App wih JWT Authentication and other DRF stuff

Django Queries App with JWT authentication, Class Based Views, Serializers, Swagger UI, CI/CD and other cool DRF stuff API Documentaion /swagger - Swa

Rafael Salimov 4 Jan 29, 2022
The ultimate Python library in building OAuth, OpenID Connect clients and servers. JWS,JWE,JWK,JWA,JWT included.

Authlib The ultimate Python library in building OAuth and OpenID Connect servers. JWS, JWK, JWA, JWT are included. Authlib is compatible with Python2.

Hsiaoming Yang 3.4k Jan 04, 2023