How to customize flask.ext.security.forms.LoginForm to prompt and use username instead of email?

1.5k Views Asked by At

How to customize flask.ext.security.forms.LoginForm to prompt and use username instead of email?

I have tried this,

class ExtendedLoginForm(LoginForm):
    name = TextField('User Name:', [Required()])
    del LoginForm.email

security = Security(app, user_datastore, login_form=ExtendedLoginForm)

Changed the template,

<form action="{{ url_for_security('login') }}" method="POST" name="login_user_form">
    {{ login_user_form.hidden_tag() }}
    **{{ render_field_with_errors(login_user_form.name) }}**
    {{ render_field_with_errors(login_user_form.password) }}
    {{ render_field_with_errors(login_user_form.remember) }}
    {{ render_field(login_user_form.next) }}
    {{ render_field(login_user_form.submit) }}
</form>

changed userdb to use name instead of email

But still flask-security tries to find email in datastore instead of user.

2

There are 2 best solutions below

2
On

In earlier versions of flask-security, the unique identifier of the user was the email property. You could extend models by adding your own properties, but the email field still served to uniquely identify the user (so the username had to be the email field). From version 1.7.0 it is possible to identify user with a different property. In the config file you should add the

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

But you still have to have the email property in your model. There was a issue reported 3 days on git, for exactly what you are looking for https://github.com/mattupstate/flask-security/issues/396

1
On

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

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    name = 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'] = 'name'

Next, to allow users to login using a name 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 name 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 name form field) to the ExtendedLoginForm class and to update the login template accordingly (as done by Raja R above). Or even better, to replace the 'email' form field in 'LoginForm' with a 'user_identity' field and rewrite the validate method to accommodate any user identity field.

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

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255), unique=True)
    name = 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'] = ('name','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 name in the email form field.