0

I'm using flask-login to handle authentication for my app, which is an API that expects HTTP Basic Authorization headers as part of each request (so the user can login and make the request without worrying about sessions, cookies, or having to do the login and requesting in separate steps).

My requests are working out like this:

POST /api/group/48
GET /login?next=%2Fapi%2Fgroup%2F48
GET /api/group/48 

That is, a POST request to /api/group/48 is getting intercepted and redirected to the /login endpoint (as expected). What happens in /login is not interactive - it takes the Basic Authorization header and logs the user in.

After the login has completed, the client is redirected back to /api/group/48 - but this time as a GET request, not a POST. And in this app, the /api/group/48 endpoint is expecting only POST data, so it dies with a 405 (Method not allowed) error.

Is this the expected behavior of flask-login? How can I have it pass through the POST request as originally submitted? (or alternatively, should I be using some different architecture so that the redirect to /login, then back to /api/group/48 doesn't take place and the POST data isn't lost?)

I haven't included code, since I don't think this is a code-specific issue. But if it turns out I'm doing something wrong, I can post some sample code.

Thanks to anyone who can help.

David White
  • 1,763
  • 2
  • 16
  • 27
  • It would be difficult to try to answer your question without any sample code. Could you include a minimal example (runnable if possible) of the problem? – Quint Jul 13 '16 at 20:09
  • I don't believe that the HTTP protocol allows for a redirect like that. Once the basic auth login finishes, I think the next request in the cycle is going to be a `GET` request. – Wayne Werner Jul 13 '16 at 20:13

2 Answers2

0

This is more then a redirect (which also might still be problematic for some browsers / clients). Your client would need to remember the body of the initial request, and re-post it later. No such mechanism exists in HTTP specification.

What you can do is remember the request (in session on something similar), then do a redirect, and then process the stored request. I don't think there are any shortcuts here.

frnhr
  • 12,354
  • 9
  • 63
  • 90
0

If you really need to redirect as POST, you can do this. Suppose you have a view with redirect like this (taken shamelessly from flask-login docs):

@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')
        # next_is_valid should check if the user has valid
        # permission to access the `next` url
        if not next_is_valid(next):
            return flask.abort(400)

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

Then as this answer quotes Wikipedia:

Many web browsers implemented this code in a manner that violated this standard, changing the request type of the new request to GET, regardless of the type employed in the original request (e.g. POST). 1 For this reason, HTTP/1.1 (RFC 2616) added the new status codes 303 and 307 to disambiguate between the two behaviours, with 303 mandating the change of request type to GET, and 307 preserving the request type as originally sent.

So your answer is change redirect to flask.redirect(next or flask.url_for('index'), code=307)

Also note that you'll probably need to redirect to login view as POST also to make it work. You can make your custom unauthorized handler:

@login_manager.unauthorized_handler
def unauthorized():
    # do stuff
    return a_response
Community
  • 1
  • 1
valignatev
  • 6,020
  • 8
  • 37
  • 61