1
votes

I am using the Flask-login's @login_required decorator for some routes in my app. When navigating to those routes while not logged in I am redirected to the login page. So far so good.

The redirected url looks like this: /login?next=%2Fusers
Looks like it url-encoded the next parameter, something I haven't seen in the examples I've run across. After logging in the redirect back to next is always failing and falling back to the index page. I think this is because next is url-encoded.

Should I be going about this a different way? I'm just starting to dive into the Flask framework and working off of examples so I don't know much about the best ways to do things.

Here's an example route:

login_manager.login_view = 'login'

@app.route('users')  
@login_required  
def users():  
  return 'Test'

And my login route looks like this:

@app.route('/login')
def login():
  error = None
  next = request.args.get('next')
  if request.method == 'POST':
    username = request.form['username']
    password = request.form['password']

    if authenticate_user(username, password):
      user = User.query.filter_by(username=username).first()
      if login_user(user):
        flash("You have logged in")
        session['logged_in'] = True
        return redirect(next or url_for('index', error=error))
    error = "Login failed"
  return render_template('login.html', login=True, next=next, error=error)

Thanks in advance for the help.

4
Note that this exposes a security vulnerability: if I send you a link like /login?next=http%3A%2F%2Fbadsite.com%2F, you'll redirect your user to that external page after they log in, which will ask them to "log in again" and grab their password. Flask, sadly, is really lacking when it comes to authentication and similar boilerplate code.Blender
Yes, checking for that is also on the todo list, I'm just working on baby steps right now.Kevan Ahlquist
The real problem was the form template's action field. It looks like redirect handles encoded paths without any problems.Kevan Ahlquist

4 Answers

3
votes

I figured it out, navigating to a login-protected route (/requests in this example) would cause a redirect to the login page with the next parameter.

/login?next=%2Frequests  

In my login template, I had this:

<form action="{{ url_for('login') }}" method='POST'>

This caused the form to be posted to the /login route without any parameters. Removing the action attribute or changing it to action="" causes the form to be posted to its own url, which includes the original query string. Another option would be including next=next in url_for.

<form action="{{ url_for('login', next=next) }}" method='POST'>
0
votes

Here you go:

import urllib


@app.route('/login')
def login():
    error = None
    next = urllib.unquote_plus(request.args.get('next'))
    ...
0
votes

In case anyone finds this question like I did, here was the implementation that worked for me. The issue was that the login's POST form was only going to /login, causing the next URL is get thrown away. Following Kevan's suggestion, if you add next=request.args.get('next')) to the login form's action, it will pass the next argument with it.

Inside my login function

@app.route('/login', methods = ['GET', 'POST'])
def login():
    next = request.args.get('next')
    if request.method == 'POST':
        ...Confirm User...
            if next:
                return redirect(next)
            else:
                return redirect('/')

Inside my login form

<form method="post" action="{{ url_for('login', next=request.args.get('next')) }}" enctype="multipart/form-data">
0
votes

For security reasons, it should be considered the check for the next parameter, as suggested in

https://flask-login.readthedocs.io/en/latest/#login-example

@app.route('/login', methods=['GET', 'POST'])
  def login():
    # Here we use a class of some kind to represent and validate our
    # client-side form data. For example, WTForms is a library that will
    # handle this for us, and we use a custom LoginForm to validate.
    form = LoginForm()
    if form.validate_on_submit():
        # Login and validate the user.
        # user should be an instance of your `User` class
       login_user(user)

       flask.flash('Logged in successfully.')

       next = flask.request.args.get('next')
       # is_safe_url should check if the url is safe for redirects.
       # See http://flask.pocoo.org/snippets/62/ for an example.
       if not is_safe_url(next):
           return flask.abort(400)

     return flask.redirect(next or flask.url_for('index'))
  return flask.render_template('login.html', form=form)