Simple flask_login example – is this the correct way?

0

Issue

Below I tried to create a very reductive and simple flask_login implementation. While it works, I just want to make sure I’m doing it the correct way. In particular, is my password authentication method correct? By correct I mean, should I be using another flask_login function to do this check for me? Do I have any unnncessary code here?

Still new to python and flask, any advice/edits would be appreciated.

Username/password check:

 if (username, password) in users_db.items():
            login_user(User(username))
            return redirect(request.args.get("next"))
        else:
            return abort(401)
    else:
        return Response('''
        <form action="" method="post">
            <p><input type=text name=username>
            <p><input type=password name=password>
            <p><input type=submit value=Login>
        </form>
        '''

Entire flask_login attempt:

from flask import Flask, jsonify, render_template, request, url_for, redirect, session, abort, Response
from flask_login import LoginManager, UserMixin, login_required, login_user, logout_user 


from flask_wtf import Form
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired


#################### Instantiate APP ###################################

'''Application Factory'''
app = Flask(__name__)

app.config['SECRET_KEY'] = 'shhsecret'   #make this more random and secret, i recommend using os.urandom(50) 
#################### Authentication ####################################

# flask-login
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = "login"


class LoginForm(Form):
    username = StringField('Your username', validators=[DataRequired()])
    password = PasswordField('Your password', validators=[DataRequired()])
    submit = SubmitField('Sign In')


# silly user model
class User(UserMixin):

    def __init__(self, username):

        self.id = username
        self.password = users_db[username]
        
    def __repr__(self):
        return "%s/%s" % ( self.id, self.password)
    
    def is_active(self):
        return True

#users database (used dictionary just as an example) 
users_db = { 'bill':'password1'
            ,'jondoe': 'password2'
            ,'elonmusk' : 'passwordtesla'

}

# create users from our users database above     
users_activated = {User(key) for (key,value) in users_db.items()} 



  

# some protected url
@app.route('/protectedurl')
@login_required
def protectedurl_func():
    return Response("Hello World!")

 

# somewhere to login
@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']        
        if (username, password) in users_db.items():
            login_user(User(username))
            return redirect(request.args.get("next"))
        else:
            return abort(401)
    else:
        return Response('''
        <form action="" method="post">
            <p><input type=text name=username>
            <p><input type=password name=password>
            <p><input type=submit value=Login>
        </form>
        ''')


# somewhere to logout
@app.route("/logout")
@login_required
def logout():
    logout_user()
    return Response('<p>Logged out</p>')


# handle login failed
@app.errorhandler(401)
def page_not_found(e):
    return Response('<p>Login failed</p>')


# callback to reload the user object        
@login_manager.user_loader
def load_user(userid):
    return User(userid)



if __name__ == '__main__':
    app.run(debug=True, use_reloader=True)






Solution

Everything works. However, there are a few principles you should consider when you are handling user data:

  • Passwords should never be stored in the database as was given by the user
  • For code re-usability, consider separation of concerns

Werkzeug is normally used to do password hashing. When a password has been “hashed” it means it has been turned into a scrambled representation of itself.

This is how it works in a python shell:

>>> from werkzeug.security import generate_password_hash
>>> hash = generate_password_hash('my_password')
>>> hash
'pbkdf2:sha256:150000$aRIbsDyl$90ae44b1a5c679e08685c75ff0750df7c6670582a5839072d35a713316816760'
>>>

my_password has been transformed into a long encoded string through a series of cryptographic operations that have no known reverse operation, which means that a person that obtains the hashed password will be unable to use it to obtain the original password.

To verify a user’s password, you can do:

>>> from werkzeug.security import check_password_hash
>>> check_password_hash(hash, 'my_password')
True
>>> check_password_hash(hash, 'another_password')
False
>>>

This password hash is what you should store in your database:

from app import db

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    password_hash = db.Column(db.String(128))

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

You can now create the login logic in your routes:

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.username.data).first()
        if user is None or not user.check_password(form.password.data):
            flash('Invalid username or password')
            return redirect(url_for('login'))
        login_user(user, remember=form.remember_me.data)
        return redirect(url_for('index'))
    return render_template('login.html', title='Sign In', form=form)

The validation checks basically try to find out if the user already exists in the database. If they do, and their username and password is correct, then they are logged in. Otherwise, they are redirected to try logging in again.

As far as separation of concerns goes, what you want to do is you ensure that your application is built using modules. I mean, there is a module that handles database issues, another that handles views, another errors et cetera.

Answered By – Gitau Harrison

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave A Reply

Your email address will not be published.

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More