Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

flask-security login via username and not email

I wanted to have the field in User model that through it the user logs in as username instead of email

I defined:
app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = 'username'

But I'm still getting:

user_datastore.add_role_to_user(name, 'mgmt')
      File "/Users/boazin/sentinal/sentinel-cloud/.env/lib/python2.7/site-packages/flask_security/datastore.py", line 105, in add_role_to_user
        user, role = self._prepare_role_modify_args(user, role)
      File "/Users/boazin/sentinal/sentinel-cloud/.env/lib/python2.7/site-packages/flask_security/datastore.py", line 72, in _prepare_role_modify_args
        user = self.find_user(email=user)
      File "/Users/boazin/sentinal/sentinel-cloud/.env/lib/python2.7/site-packages/flask_security/datastore.py", line 203, in find_user
        return self.user_model.query.filter_by(**kwargs).first()
      File "/Users/boazin/sentinal/sentinel-cloud/.env/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 1333, in filter_by
        for key, value in kwargs.items()]
      File "/Users/boazin/sentinal/sentinel-cloud/.env/lib/python2.7/site-packages/sqlalchemy/orm/base.py", line 383, in _entity_descriptor
        (description, key)
    InvalidRequestError: Entity '<class 'flask_app.models.User'>' has no property 'email'

It seems that email is hardcoded into flask-security...

Can I change it?

edit: The User Model (as requested in the comment):

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(255), unique=True, index=True)
    password = db.Column(db.String(255))
    token = db.Column(db.String(255), unique=True, index=True)
    active = db.Column(db.Boolean())
    confirmed_at = db.Column(db.DateTime())
    roles = db.relationship('Role', secondary=roles_users,
                            backref=db.backref('users', lazy='dynamic'))
like image 929
Boaz Avatar asked Jun 14 '15 09:06

Boaz


People also ask

How do I know if someone logged into my Flask?

At the same time, you can use Flask-login API to do some configurations in case that you want to use its functionality. When you want to check whether the user has logged in manually rather than use the Flask-login API, then check the value of session['logged_in'] .


3 Answers

To login with a username instead of an email address (using Flask-Security 1.7.0 or higher), you can replace the email field with a username field in the User model

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(255), unique=True, index=True)
    password = db.Column(db.String(255))
    active = db.Column(db.Boolean())
    confirmed_at = db.Column(db.DateTime())
    roles = db.relationship('Role', secondary=roles_users,
                            backref=db.backref('users', lazy='dynamic'))

and update the app configuration.

app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = 'username'

Next, to allow users to login using a username instead of an email, we will use the fact that the LoginForm validation method assumes the user identity attribute is in the email form field.

from flask_security.forms import LoginForm
from wtforms import StringField
from wtforms.validators import InputRequired

class ExtendedLoginForm(LoginForm):
    email = StringField('Username', [InputRequired()])

# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore,
                    login_form=ExtendedLoginForm)

This way, we can login using a username without rewriting the validation method or the login template. Of course, this is a hack and the more correct approach would be to add a custom validate method, which checks a username form field, to the ExtendedLoginForm class and to update the login template accordingly.

However, the approach above makes it easy to login with a username or an email address. To do this, define a user model with both a username and email field.

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255), unique=True)
    username = db.Column(db.String(255), unique=True, index=True)
    password = db.Column(db.String(255))
    active = db.Column(db.Boolean())
    confirmed_at = db.Column(db.DateTime())
    roles = db.relationship('Role', secondary=roles_users,
                            backref=db.backref('users', lazy='dynamic'))

and update the app configuration.

app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = ('username','email')

Finally, create the custom login form.

from flask_security.forms import LoginForm
from wtforms import StringField
from wtforms.validators import InputRequired

class ExtendedLoginForm(LoginForm):
    email = StringField('Username or Email Address', [InputRequired()])

# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore,
                    login_form=ExtendedLoginForm)

Now, when logging in, Flask-Security will accept an email or username in the email form field.

like image 160
eric Avatar answered Sep 25 '22 19:09

eric


I managed to implement login using either username or password by overwriting the login form:

class ExtendedLoginForm(LoginForm):
    email = StringField('Username or Email Address')
    username = StringField("Username")

    def validate(self):
    from flask_security.utils import (
        _datastore,
        get_message,
        hash_password,
    )
    from flask_security.confirmable import requires_confirmation
    if not super(LoginForm, self).validate():
        return False

    # try login using email
    self.user = _datastore.get_user(self.email.data)

    if self.user is None:
        self.user = _datastore.get_user(self.username.data)

    if self.user is None:
        self.email.errors.append(get_message("USER_DOES_NOT_EXIST")[0])
        # Reduce timing variation between existing and non-existing users
        hash_password(self.password.data)
        return False
    if not self.user.password:
        self.password.errors.append(get_message("PASSWORD_NOT_SET")[0])
        # Reduce timing variation between existing and non-existing users
        hash_password(self.password.data)
        return False
    if not self.user.verify_and_update_password(self.password.data):
        self.password.errors.append(get_message("INVALID_PASSWORD")[0])
        return False
    if requires_confirmation(self.user):
        self.email.errors.append(get_message("CONFIRMATION_REQUIRED")[0])
        return False
    if not self.user.is_active:
        self.email.errors.append(get_message("DISABLED_ACCOUNT")[0])
        return False
    return True

and register it as described in other posts:

# Setup Flask-Security
app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = ('username','email')
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore,
                login_form=ExtendedLoginForm)

Since email and username are optional now one of them can be used to login. But make sure that both fields are set unique in the DB model.

like image 25
NMO Avatar answered Sep 22 '22 19:09

NMO


From https://pythonhosted.org/Flask-Security/models.html

Fields id, email, password, active is essential.So add

email = db.Column(db.String(255), unique=True)

Just add your custom username field along this.

like image 36
itzMEonTV Avatar answered Sep 24 '22 19:09

itzMEonTV