Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
nickjj opened this issue Mar 25, 2021 · 4 comments

Comments

@nickjj
Copy link

nickjj commented Mar 25, 2021

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?

@vimalloc
Copy link
Owner

vimalloc commented Mar 29, 2021

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.

Yeah, that was something that got changed. In 3.x.x, current_user used to return None if called without calling @jwt_required() first. I changed that in 4.x.x because a bunch of people ran into errors where things were not working when they forgot to call jwt_required, or had it in the wrong order for decorators, etc.

A stupid and yet easy way to work around this might be to add a @jwt_required(optional=true) to your login endpoint, which will set things correctly so that current_user returns None if no JWT is present in the request. Alternatively, you could remove the current_user call from the login form, because as your code currently stands there will never be a current_user set when logging in.

Or perhaps we could introduce a new method that checks if there is a current user, or a setting that lets you toggled the behavior of current_user back to how it worked in 3.x.x. I'm generally not a fan of adding new settings, but I'm also not a huge fan of needing @jwt_required(optional=true) just to make the jinja template easier to manage.

Regardless, that error message is totally misleading. It should be saying something like You must call @jwt_required() or verify_jwt_in_request before using this method. I'll get that fixed up to avoid confusion in the future 👍

@nickjj
Copy link
Author

nickjj commented Mar 31, 2021

What if the error said something like You are referencing current_user in a view that's not decorated with @jwt_required() or @jwt_required(optional=True).

I think if I saw that it would have saved me around 30 minutes of debugging.

@vimalloc
Copy link
Owner

That sounds great to me. Thanks for the feedback! 👍

@vimalloc vimalloc closed this as completed May 2, 2021
@vimalloc
Copy link
Owner

vimalloc commented May 2, 2021

Fixed in v4.2.0!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants