How do I pass through the "next" URL with Flask and Flask-login?

Just in case someone is trying to pass through the "next" URL with Flask-Login but with Flask_Restful, the workaround I've been using is passing the "next" argument from the GET method to the POST method. With flask_restful the "next_page" argument is set to "None" after clicking on the Login Button in the login.html

login.html

...
<!-- next_page came from "render_template(next_page=request.args.get('next') ...)" in the get() function -->
<!-- And also from render_template('login.html', next_page=next_page) in the post() function -->
<form action="{{ url_for('login', next=next_page) }}" method="POST" >
    <div class="field">
        <div class="control">
            <input class="input is-large" type="email" name="email" placeholder="Your Email" autofocus="">
        </div>
    </div>

    <div class="field">
        <div class="control">
            <input class="input is-large" type="password" name="password" placeholder="Your Password">
        </div>
    </div>
    <div class="field">
        <label class="checkbox">
            <input type="checkbox" name="remember_me">
            Remember me
        </label>
    </div>
    <button class="button is-block is-info is-large is-fullwidth">Login</button>
</form>
...

auth.py

class Login(Resource):

    def get(self):
        if current_user.is_authenticated:
            return redirect(url_for('home'))

        headers = {'Content-Type': 'text/html'}

#1 -->  # Here I pass the "next_page" to login.html
        return make_response(render_template('login.html', next_page=request.args.get('next')), 200, headers)

    def post(self):
#2 -->  # After the user clicks the login button, I retrieve the next_page saved in the GET method
        next_page = request.args.get('next')

        if current_user.is_authenticated:
            return redirect(url_for('home'))

        # Check if account exists in the db
        existing_account = Account.objects(email=request.form.get('email')).first()

        # Only redirects when the URL is relative, which ensures that the redirect 
        # stays within the same site as the application.
        if existing_account:
            if existing_account.check_password(request.form.get('password')):
                login_user(existing_account, remember=request.form.get('remember_me'))

                if not next_page or url_parse(next_page).netloc != '':
                    return redirect(url_for('home'))

#3 -->          # Here I use the retrieved next_page argument
                return redirect(url_for(next_page))

        # Account not recognized
        flash('Please check your login details and try again.')
        headers = {'Content-Type': 'text/html'}

#4 -->  # I also pass the "next_page" here in case the user-provided data is wrong
        return make_response(render_template('login.html', next_page=next_page), 200, headers)

@timakro provides a neat solution. If you want to handle a dynamic link such as

index/<user>

then using url_for(request.endpoint,**request.view_args) instead because request.endpoint will not contain the dynamic vairable info:

 @login_manager.unauthorized_handler
 def handle_needs_login():
     flash("You have to be logged in to access this page.")
     #instead of using request.path to prevent Open Redirect Vulnerability 
     next=url_for(request.endpoint,**request.view_args)
     return redirect(url_for('account.login', next=next))

the following code shall be changed to:

def redirect_dest(home):
    dest_url = request.args.get('next')
    if not dest_url:
        dest_url = url_for(home)
    return redirect(dest_url)

@app.route("/login", methods=["GET", "POST"])
def login():
    ...
    if user_is_logged_in:
        flash("Logged in!")
        return redirect_dest(home=anyViewFunctionYouWantToSendUser)
    else:
        flash("Sorry, but you could not log in.")
        return render_template("login.html")

Out of all the above answers I didn't find anything helpful for me. But what I found, I will share with you..

In login.html just add the below code

<input
    type="hidden"
    name="next"
    value="{{ request.args.get('next', '') }}"
/>

And login definition changes as follows

@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        username = request.form.get("username")
        password = request.form.get("password")
        next_url = request.form.get("next")

        if username in users and users[username][1] == password:
            session["username"] = username
            if next_url:
                return redirect(next_url)
            return redirect(url_for("profile"))
    return render_template("login.html")

And This worked perfectly for me...

Thank you ...


If you need to specify a different action attribute in your form you can't use the next parameter provided by Flask-Login. I'd recommend anyways to put the endpoint instead of the url into the url parameter since it is easier to validate. Here's some code from the application I'm working on, maybe this can help you.

Overwrite Flask-Login's unauthorized handler to use the endpoint instead of the url in the next parameter:

@login_manager.unauthorized_handler
def handle_needs_login():
    flash("You have to be logged in to access this page.")
    return redirect(url_for('account.login', next=request.endpoint))

Use request.endpoint in your own URLs too:

{# login form #}
<form action="{{ url_for('account.login', next=request.endpoint) }}" method="post">
...
</form>

Redirect to the endpoint in the next parameter if it exists and is valid, else redirect to a fallback.

def redirect_dest(fallback):
    dest = request.args.get('next')
    try:
        dest_url = url_for(dest)
    except:
        return redirect(fallback)
    return redirect(dest_url)

@app.route("/login", methods=["GET", "POST"])
def login():
    ...
    if user_is_logged_in:
        flash("Logged in!")
        return redirect_dest(fallback=url_for('general.index'))
    else:
        flash("Sorry, but you could not log in.")
        return render_template("login.html")