3

I'm using Ajax to login, using Flask-Login extension. Here's my server side code:

@app.route('/login', methods=["POST"])
def login():
    if current_user.is_authenticated:
        redirect_url = url_for('index')
        return jsonify(loggedIn=True, redirectUrl=redirect_url)

    username = request.form.get('username', '').strip()
    password = request.form.get('password', '').strip()
    user = User.query.filter_by(username=username).first()
    if user and util.encrypt_password(password, user.salt) == user.password:
        logged_in_user = CurrentUser(user)
        login_user(logged_in_user)
        redirect_url = url_for('index')
        return jsonify(loggedIn=True, redirectUrl=redirect_url)
    else:   
        return jsonify(loggedIn=False, error='Invalid Email/Password')

and my client side code:

(function(){
        $login_form = $('#login_form');

        //add validation to form
        if( $login_form.length ){

            $login_form.parsley()
            $login_form.submit(function(e) {
                var url = $(this).attr('action');
                var data = $(this).serialize();

                tryLogin(url, data);
                return false;
            }); 
        }

       function tryLogin(url, data){
        var $submitBtn = $('#login_form__submit');

        //notify user that we are working
        $submitBtn.addClass('btn--loading');

        $.ajax({
            type: 'POST',
            url: url,
            data: data,
            dataType: 'json',
            success: function (data) {
                if (data.loggedIn) {
                    mixpanel.track('login_success');
                    window.location.href = data.redirectUrl || '/';
                } 
             }
         });
      }
});

Not sure what's wrong, I have started seeing this problem lately, and it fails more than 50% of times. In case of failure, it'll just redirect to the new page but no session info exist. All the content is hosted on the same domain.

EDIT: More info: even when the login doesn't work intermittently, the backend does pass the login and frontend receives loggedIn=True and redirectUrl. Seems like issue is with session/cookie being received by the client but not sure why the intermittent issue.

UPDATE

Wholever is reading this now. I couldn't find a good solution. But debugged and realized this was definitely happening due to some bug in Chrome to update cookie/session data from the Ajax request. I resolved this by moving to server-side sessions using redis instead of client side. That made sure the request always have the right session info.

Ankit
  • 3,878
  • 5
  • 35
  • 51
  • does your util provide a match method for the password? Internally the method could return different password hashes for the same salt each invocation. Have you tried the werkzeug library [link to werkzeug](http://flask.pocoo.org/snippets/54/) – Jase Rieger Mar 03 '16 at 11:33
  • In the JavaScript you should change the line `success: function (data) {` to `success: function (response) {` that way there is a clear distinction between the input data and the returned Ajax data. – Jase Rieger Mar 03 '16 at 13:19

4 Answers4

2

By default, the URL router in Flask only response to GET method. Your are using POST method to make the request in AJAX.

Seems like your view function should handle both GET and POST (when this URL is accessed by a logged-in user, just redirect to index page?)

So you have to explicitly set the methods parameter to @app.route, change this line

@app.route('/login')

to:

@app.route('/login', methods=['GET', 'POST'])

and try again.

If not working, please comment and I'll keep updating.

kagami
  • 592
  • 4
  • 15
  • Thanks for catching that, I missed that out, I actually keep the paths in a different file and had methods=['POST'], I replaced that with methods=['POST', 'GET'] but didn't solve the problem... – Ankit Feb 26 '16 at 04:16
  • @Ankit Have you tried to see the `JSON` return of the handler in browser? – kagami Feb 26 '16 at 19:24
2

Can you modify the code to use the werkzeug library?

Unless your util has a check_password method. Internally the create hash method could return different password hashes for the same salt each invocation. Which explains intermittent problems, because sometimes the returned hash matches, sometimes it doesnt. I believe it can all depend on the random function, which is usually tied to clock, and the internal hashing algorithm.

Theres no need to store the password hash with werkzeug as it is stored within the hash itself and salted. I have used the werkzeug library myself for logging in, and the below code should work. Provided you use the generate_password_hash provided by werkzeug to generate the password hash and store than in the database. Werkzeug library

Your code using werkzeug:

from werkzeug.security import generate_password_hash, \
     check_password_hash
@app.route('/login', methods=["POST"])
def login():
    if current_user.is_authenticated:
        redirect_url = url_for('index')
        return jsonify(loggedIn=True, redirectUrl=redirect_url)

    username = request.form.get('username', '').strip()
    password = request.form.get('password', '').strip()
    user = User.query.filter_by(username=username).first()
    if user and check_password_hash(user.password, password):
        logged_in_user = CurrentUser(user)
        login_user(logged_in_user)
        redirect_url = url_for('index')
        return jsonify(loggedIn=True, redirectUrl=redirect_url)
    else:   
        return jsonify(loggedIn=False, error='Invalid Email/Password')

An example from the console:

>>> from werkzeug.security import generate_password_hash, check_password_hash
>>> password = 'cat'
>>> a = generate_password_hash(password)
>>> b = generate_password_hash(password)
>>> c = generate_password_hash(password)
>>> for i in (a, b, c):
...   print i
...
pbkdf2:sha1:1000$NdpMiLKu$7c24f73766856ac94102733ae9bf93c0f79b5e53
pbkdf2:sha1:1000$dh2mj93K$61103a04a1bcb16e95a44ca3615518b12fecafc1
pbkdf2:sha1:1000$wdGPkX4p$9db55c5035e9c71f7e1e14ece6be619bf9fdaccf
>>> for i in (a, b, c):
...   print check_password_hash(i, password)
...
True
True
True
>>> print check_password_hash(a, 'dog')
False
Jase Rieger
  • 611
  • 3
  • 8
  • thanks but I've verified that password isn't a problem, has to be the cookies coz I have checked the response returned from the server and it sends as loggedIn=True with correct redirect url. – Ankit Mar 04 '16 at 20:49
  • Ina shell when you do `for i in range(10): util.encrypt_password(password, user.salt) ` is the password hash always the same? It shouldn't be. Unless the library is unsecure – Jase Rieger Mar 04 '16 at 23:51
  • why is would that not be same? This is not a library function but something I have written. If salt is provided, I add that to password and do a hash on it. It should be the same to validate password. – Ankit Mar 05 '16 at 00:18
  • Why did you choose to use your own instead of the standard libraries? – Jase Rieger Mar 05 '16 at 00:19
  • coz I am migrating from an existing system built in PHP. Is has to be a preset way to generate and store passwords. – Ankit Mar 05 '16 at 00:22
1

I had the problem where Flask Login would not log me in if I made a post request through AJAX, but it would work if I made a GET request or POSTed from a form. It turns out that the URL I was posting to was http://0.0.0.0:8000/... instead of http://localhost:8000/... . Changing the url to http://localhost:8000/ fixed my issue.

struny
  • 11
  • 4
0

Try to check that it isn't a jQuery bug related to working with cookies in Ajax requests: https://stackoverflow.com/a/7189502/882187

Also, try to make redirection after short timeout, to be sure that it occurs outside the ajax callback handler. This issue could be intermittent because of the callback in JS. What if redirect with 'window.location.href' breaks the cookie processing handler?..

Community
  • 1
  • 1
Vladimir Ignatev
  • 2,054
  • 1
  • 20
  • 34