Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

The documentation for Flask-login talks about handling a "next" URL. The idea seems to be:

  1. User goes to /secret
  2. User is redirect to a login page (e.g. /login)
  3. After a successful login, the user is redirected back to /secret

The only semi-full example using Flask-login that I've found is https://gist.github.com/bkdinoop/6698956 . It's helpful, but since it doesn't include the HTML template files, I'm seeing if I can recreate them as an self-training exercise.

Here's a simplified version of the /secret and /login section:

@app.route("/secret") @fresh_login_required def secret():     return render_template("secret.html")  @app.route("/login", methods=["GET", "POST"]) def login():     <...login-checking code omitted...>     if user_is_logged_in:         flash("Logged in!")         return redirect(request.args.get("next") or url_for("index"))     else:         flash("Sorry, but you could not log in.")         return render_template("login.html") 

And here's login.html:

<form name="loginform" action="{{ url_for('login') }}" method="POST"> Username: <input type="text" name="username" size="30" /><br /> Password: <input type="password" name="password" size="30" /><br /> <input type="submit" value="Login" /><br /> 

Now, when the user visits /secret, he gets redirected to /login?next=%2Fsecret . So far, so good - the "next" parameter is in the query string.

However when the user submits the login form, he is redirected back to the index page, not back to the /secret URL.

I'm guessing the reason is because the "next' parameter, which was available on the incoming URL, is not incorporated into the login form and is therefore not being passed as a variable when the form is processed. But what's the right way to solve this?

One solution seems to work - change the <form> tag from

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

to:

<form name="loginform" method="POST"> 

With the "action" attribute removed, the browser (at least Firefox 45 on Windows) automatically uses the current URL, causing it to inherit the ?next=%2Fsecret query string, which successfully sends it on to the form processing handler.

But is omitting the "action" form attribute and letting the browser fill it in the right solution? Does it work across all browsers and platforms?

Or does Flask or Flask-login intend for this to be handled in a different way?

like image 629
David White Avatar asked Mar 28 '16 19:03

David White


People also ask

How do I redirect to another page after login in Flask?

Python for web development using FlaskFlask class has a redirect() function. When called, it returns a response object and redirects the user to another target location with specified status code. location parameter is the URL where response should be redirected. statuscode sent to browser's header, defaults to 302.

How do you pass data into a URL Flask?

If you are using the first route, the url should look like http://127.0.0.1/changeip/1.2.2.2 . If you are using the second url, the route should look like /changeip , the function should be def change_ip(): , and the value should be read from request. args['ip'] .


2 Answers

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") 
like image 149
timakro Avatar answered Oct 13 '22 01:10

timakro


@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") 
like image 37
Kurumi Tokisaki Avatar answered Oct 13 '22 00:10

Kurumi Tokisaki