1

This view receives the ?next= argument when making a GET request but once the user makes a POST request to the same view the argument is lost. The examples I've seen don't explicitly pass on the query arguments but seem to be able to retain them and get them in the following POST request.

@blueprint.route("/login", methods=["GET", "POST"])
@logout_required()
def login():
    form = LoginForm()
    #1
    print("NEXT:", request.args.get("next"))

    if form.validate_on_submit():
        u = User.query.filter_by(username=form.username.data).first()
        if u and u.check_password(password=form.password.data):
            if u.is_active():
                #2
                print("NEXT:", request.args.get("next"))

                login_user(u, remember=form.remember.data)

                next_url = request.args.get("next")
                if not next_url or url_parse(next_url).netloc != "":
                    next_url = url_for("main.index")

                return redirect(next_url)
            else:
                flash("This account has been disabled", "error")
        else:
            flash("Invalid password or username.", "error")

    return render_template("auth/login.html", form=form)
raccoons
  • 420
  • 3
  • 16
  • See https://stackoverflow.com/questions/26954122/how-can-i-pass-arguments-into-redirecturl-for-of-flask – Dave W. Smith Jul 02 '20 at 17:15
  • Thanks for the link. I've managed to narrow down the issue coming from how I submit the POST request in the `
    `. When I set the `
    ` action attribute to an empty string, it retains the original request arguments and passes them on to the view function. I'm not sure in what way it's doing this and wonder if there's an explicit way of setting the action to pass forward the arguments.
    – raccoons Jul 02 '20 at 17:36

2 Answers2

0

you are missing two redirections with return redirect() after last the two flash() functions, see the code below:

@blueprint.route("/login", methods=["GET", "POST"])
@logout_required()
def login():
    form = LoginForm()
    #1
    print("NEXT:", request.args.get("next"))

    if form.validate_on_submit():

        next_url = request.args.get("next")  #  -- HERE -- get "?next argument"

        u = User.query.filter_by(username=form.username.data).first()
        if u and u.check_password(password=form.password.data):
            if u.is_active():
                #2
                print("NEXT:", request.args.get("next"))

                login_user(u, remember=form.remember.data)

                # next_url = request.args.get("next")  # -- HERE -- 
                if not next_url or url_parse(next_url).netloc != "":
                    next_url = url_for("main.index")

                return redirect(next_url)

            else:
                flash("This account has been disabled", "error")
                return redirect(url_for('auth.index'))  # -- HERE --

        else:
            flash("Invalid password or username.", "error")
            return redirect(url_for('auth.login', next=next_url))  # -- HERE --

    return render_template("auth/login.html", form=form)

i assume /main is the protected route and there's currently no session, the user is not logged in yet, so when the user try to directly access

http://localhost:5000/admin

he will be automatically redirected to this url (auth.login route with an argument ?next=%2Fadmin%2F)

http://localhost:5000/auth/login?next=%2Fadmin%2F

with the default flask-login flash() message Please log in to access this page. then he could login, if the login was successful he will be logged in and redirected to the protected area using ?next argument,

but if it happens and the temptation fails, we have two options:

  • if Invalid password or username he will be redirected the auth.login route with ?next=%2Fadmin%2F argument for second chance.

  • but if This account has been disabled he will be redirected the auth.login route without ?next=%2Fadmin%2F argument and it does make sens.

and finally your don't need to worry about the value of ?next= because the user could play with it or even remove it and this block

                if not next_url or url_parse(next_url).netloc != "":
                    next_url = url_for("main.index")

                return redirect(next_url)

defaults to main.index if any.

cizario
  • 3,995
  • 3
  • 13
  • 27
  • That's not the issue I'm having. Those flashes pass through and re-render the template, acting as a redirect to the same view function. – raccoons Jul 02 '20 at 17:31
  • `flash()` doesn't perform **redirections**, you need add them explicitly, just try the code and then let see. – cizario Jul 02 '20 at 17:34
  • I know `flash()` doesn't perform redirections. In this case I want the user to reload onto the same view with the flashed messages which is achieved through the final `render_template()` on the function. The second `redirect()` you added would be no difference in behavior to what is happening now and the first `redirect()` you added is not where I want them to be redirected. – raccoons Jul 02 '20 at 17:42
  • i updated my answer and let me know if that's exactly what you want. i marked updated lines with `-- HERE --` – cizario Jul 02 '20 at 18:49
  • Your first change `next_url = request.args.get("next") # -- HERE -- get "?next argument"` is what I was originally having problems with as it was getting lost on the POST request. I've put up the solution I found to the problem. It also deals with the issue of losing the ?next argument as it is preserved within a hidden field. – raccoons Jul 02 '20 at 20:30
0

Figured out the issue was basically because I was mixing GET request arguments with POST input submissions.

for my login url http://localhost:8000/auth/login?next=%2Fadmin

<form action="">

will send a POST request to the login view function and allow access through request.args.get()

However, the proper way to capture and pass on the GET request next argument would be through a hidden input field that holds the initial GET request's next argument, allowing it to be passed properly as part of the form in the POST request.

<form action="{{ url_for('auth.login' }}">
    <input id="next" name="next" type="hidden" value="/admin">

The view function:

@blueprint.route("/login", methods=["GET", "POST"])
@logout_required()
def login():
    form = LoginForm(next=request.args.get("next"))
raccoons
  • 420
  • 3
  • 16
  • For reference, I came across this issue following the Flask Mega Tutorial, part V to be specific. Didn't think changing the form action attribute would cause the behavior change on how the POST request passes on GET request arguments. – raccoons Jul 02 '20 at 18:17