13

In short:

By only using the Flask micro-framework (and its dependencies) can we perform an internal redirect from one route to another?

For example:

  1. User submits the registration form (both username and password) to @app.route('/register', methods=['POST'])
  2. If the registration is successful, Flask internally does an HTTP POST to @app.route('/login', methods['POST']) passing the username and password
  3. Process and log in the user

Details:

I am building a REST API using Flask and the Flask-JWT extension. More specifically I'm implementing the login and registration.

Login works perfectly and returns a JSON object with a token.

Following is my (login) authentication handler (i.e. /auth (POST request) - Default Flask-JWT authentication URL rule):

@jwt.authentication_handler
def authenticate(username, password):
    user = User.query.filter_by(username=username).first()
    if user and user.verify_password(password):
        return user
    return None

A successful login returns:

{
  "token": "<jwt-token>"
}

Following is my registration route:

@app.route('/register', methods=['PUT'])
def register():
    username = request.form.get('username')
    password = request.form.get('password')
    if username is None or password is None:
        abort(400)  # missing parameters

    user = User.query.filter_by(username=username).first()
    if user:
        abort(400)  # user exists
    else:
        user = User(user=user)
        user.hash_password(password)
        db.session.add(user)
        db.session.commit()

        # How do we generate a token?
        # Perform an internal redirect to the login route?

    return jsonify({'token': <jwt-token>}), 201
grim
  • 6,669
  • 11
  • 38
  • 57
  • I dont think you can do a redirect with post data ...otherwise you may want to just look at flask.redirect ... – Joran Beasley Sep 12 '15 at 01:13
  • I think what you mean by "internal redirect" is calling login functionality from the register() function via the routing table by means setting up a fake external request. Instead of answering >that< question, I would point out that you can call non-routed python subroutines in flask. So you could write one subroutine and call it from two different routes, which is much more straightforward. By the way, it is common/normal to send a mail to a new user's email address requesting confirmation before allowing them to log in. – Craig Hicks Jun 09 '17 at 17:00

1 Answers1

9

You should use the Post-Redirect-Get pattern.

from flask import Flask, redirect, request, render_template
app = Flask("the_flask_module")

@app.route('/', methods=["GET", "POST"])
def post_redirect_get():
    if request.method == "GET":
        return render_template("post_redirect_get.html")
    else:
        # Use said data.
        return redirect("target", code=303)

@app.route("/target")
def target():
    return "I'm the redirected function"

app.run(host="0.0.0.0", port=5001)

And if you want to pass data to the target function (like that token) you can use the session object to store it

So that would break down something like

@app.route('/register', methods=['PUT'])
def register():
    username = request.form.get('username')
    password = request.form.get('password')
    if username is None or password is None:
        abort(400)  # missing parameters

    user = User.query.filter_by(username=username).first()
    if user:
        abort(400)  # user exists
    else:
        user = User(user=user)
        user.hash_password(password)
        db.session.add(user)
        db.session.commit()

        # How do we generate a token?
        redirect("login_success", code=307)

@app.route("login_success", methods=["GET", "POST"])
@jwt_required()
def login_success():
    return "Redirected Success!"

Edit: I haven't used Flask-JWT before and didn't know about the post requirement. But you can tell Flask to redirect with the current method used (rather than a get request) by passing the redirect function code=307.. Hopefully that solves your extended problem.

AlexLordThorsen
  • 8,057
  • 5
  • 48
  • 103
  • It answers my short question perfectly. But unfortunately it doesn't work in the context of Flask-JWT since the extension is handling the authentication response in a `POST` route (or at least I wasn't able to make it work with this extension) – grim Sep 13 '15 at 20:29
  • 1
    I can't agree that this answers addresses the question "can we perform an internal redirect from one route to another?", which I think clearly means without delivering a response to the client inbetween. Your code does return a response to the client. Your code is certainly the standard/best way to do it, and I am sure you know why: to prevent duplicate form submission [Grinberg, Miguel. Flask Web Development: Developing Web Applications with Python (p. 44). O'Reilly Media. Kindle Edition. ] There is a lot of confusion around the term "internal redirects". – Craig Hicks Jun 09 '17 at 16:41
  • @CraigHicks That's certainly a valid criticism. If you have a way to redirect without a client side response (that doesn't break SSL security, DNSec, or require special javascript to reload the new resources) I'm certainly all ears. – AlexLordThorsen Jun 10 '17 at 00:53