Skip to content

get_current_user() is throwing a run-time error saying a @jwt.user_lookup_loader callback must be defined, but it is defined #408

@nickjj

Description

@nickjj

Hi,

I'm upgrading from 3.x to 4.1.0 and I'm running into RuntimeError: You must provide a @jwt.user_lookup_loader callback to use this method. I'm getting this error when loading my login page which has a view of:

@user.route('/login')
def login():
    return render_template('user/login.html')

Here's the route I took to try and narrow down the error:

  • If I change the above to return 'hey' then the exception isn't thrown.
  • If I go into my login.html template and make the body block empty, then the exception is thrown.
  • If I goto my login.html template and don't pull in my layout then the exception isn't thrown.
  • If I goto my layout template and remove my nav bar (which reads current_user.username) then the exception isn't thrown and I'm able to login and everything works.

So there's something about reading that current_user that used to work in 3.x that's not working anymore in 4.x and it's causing things to blow up. The code in the template is href="{{ url_for('facts.index', username=current_user.username) }}". This works when I'm logged in but not when I'm logged out.

It used to work in both cases with 3.x. I am using JS to toggle the visibility of some menu items based on being logged in or not. I know I can likely alter the logic to check if there's a current_user at the Jinja level but I'm trying to streamline the UI a bit with JS and if a bad actor unhid the nav items as a logged out user it wouldn't do any harm. Any thoughts on how to get this to work with 4.x?

Here's what I've done so far:

In my old 3.x code, I had this in my callbacks:

def jwt_callbacks():
    @jwt.user_loader_callback_loader
    def user_loader_callback(identity):
        return User.query.filter((User.username == identity)).first()

In the new 4.x code, I've changed that to:

def jwt_callbacks():
    @jwt.user_lookup_loader
    def user_lookup_callback(_jwt_header, jwt_data):
        identity = jwt_data['sub']
        return User.query.filter((User.username == identity)).first()

In both cases this jwt_callbacks() function is being called in my create_app() function which is the set up I was using with 3.x that worked.

As for using the required decorators, this all worked in the 3.x code using this code style:

@user.route('/community')
@jwt_required
def community():
    return render_template('user/community.html')

In the 4.x upgrade I'm doing the same ordering except now I'm calling jwt_required(). In the cases where I am using optional authentication I've changed those to jwt_required(optional=True) as per the upgrade guide.

Here's the full stack trace which is interesting because it's saying I'm trying to use get_current_user() but the callback isn't defined, however that callback is defined.

web_1       | [2021-03-25 23:06:29 +0000] [27] [ERROR] Error handling request /login
web_1       | Traceback (most recent call last):
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/gunicorn/workers/sync.py", line 134, in handle
web_1       |     self.handle_request(listener, req, client, addr)
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/gunicorn/workers/sync.py", line 175, in handle_request
web_1       |     respiter = self.wsgi(environ, resp.start_response)
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/flask/app.py", line 2464, in __call__
web_1       |     return self.wsgi_app(environ, start_response)
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/flask/app.py", line 2450, in wsgi_app
web_1       |     response = self.handle_exception(e)
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/flask/app.py", line 1867, in handle_exception
web_1       |     reraise(exc_type, exc_value, tb)
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/flask/_compat.py", line 39, in reraise
web_1       |     raise value
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/flask/app.py", line 2447, in wsgi_app
web_1       |     response = self.full_dispatch_request()
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/flask/app.py", line 1952, in full_dispatch_request
web_1       |     rv = self.handle_user_exception(e)
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/flask/app.py", line 1821, in handle_user_exception
web_1       |     reraise(exc_type, exc_value, tb)
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/flask/_compat.py", line 39, in reraise
web_1       |     raise value
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/flask/app.py", line 1950, in full_dispatch_request
web_1       |     rv = self.dispatch_request()
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/flask_debugtoolbar/__init__.py", line 125, in dispatch_request
web_1       |     return view_func(**req.view_args)
web_1       |   File "/app/fakefacts/blueprints/user/views.py", line 9, in login
web_1       |     return render_template('user/login.html')
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/flask/templating.py", line 137, in render_template
web_1       |     return _render(
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/flask/templating.py", line 120, in _render
web_1       |     rv = template.render(context)
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/jinja2/environment.py", line 1090, in render
web_1       |     self.environment.handle_exception()
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/jinja2/environment.py", line 832, in handle_exception
web_1       |     reraise(*rewrite_traceback_stack(source=source))
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/jinja2/_compat.py", line 28, in reraise
web_1       |     raise value.with_traceback(tb)
web_1       |   File "/app/fakefacts/blueprints/user/templates/user/login.html", line 2, in top-level template code
web_1       |     {% import 'macros/form.html' as f with context %}
web_1       |   File "/app/fakefacts/templates/layouts/base.html", line 52, in top-level template code
web_1       |     <a class="nav-link" href="{{ url_for('facts.index', username=current_user.username) }}">
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/jinja2/environment.py", line 471, in getattr
web_1       |     return getattr(obj, attribute)
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/werkzeug/local.py", line 347, in __getattr__
web_1       |     return getattr(self._get_current_object(), name)
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/werkzeug/local.py", line 306, in _get_current_object
web_1       |     return self.__local()
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/flask_jwt_extended/utils.py", line 10, in <lambda>
web_1       |     current_user = LocalProxy(lambda: get_current_user())
web_1       |   File "/home/python/.local/lib/python3.9/site-packages/flask_jwt_extended/utils.py", line 77, in get_current_user
web_1       |     raise RuntimeError(
web_1       | RuntimeError: You must provide a `@jwt.user_lookup_loader` callback to use this method

As for settings, the only ones I've set are:

JWT_TOKEN_LOCATION = ['cookies', 'headers']
JWT_COOKIE_SECURE = False
JWT_SESSION_COOKIE = False
JWT_ACCESS_TOKEN_EXPIRES = timedelta(weeks=52)
JWT_ACCESS_COOKIE_PATH = '/'
JWT_COOKIE_CSRF_PROTECT = True

Any ideas?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions