Misc

Status

Flask-User v1.0 is quite stable and is used in production environments.
It is marked as a Beta release because the API is subject to small changes.
We appreciate it if you would enter issues and enhancement requests into the Flask-User Issue Tracker.

Demo

The Flask-User Demo showcases Flask-User. To protect against spam mis-use, all email features have been disabled. (If you’re the first visitor in the last hour, it may take a few seconds for Heroku to start this service)

Contact Information

Ling Thio - ling.thio [at] gmail.com

Feeling generous? Tip me on Gittip

Revision History

  • v1.0.0 Initial version

Extension Packages

We plan to offer the following Flask-User extensions in the future:

  • Flask-User-Profiles: View profile, Edit profile, Upload profile picture
  • Flask-User-Social: Login via Google, Facebook and Twitter authentication

Acknowledgements

This project would not be possible without the use of the following amazing offerings:

Contributors

Customize

Flask-User has been designed with full customization in mind, and and here is a list of behaviors that can be customized as needed:

Labels and Messages

The following can be customized by editing the English Babel translation file:

  • Flash messages (one-time system messages)
  • Form field labels
  • Validation messages

See Internationalization

Registration Form

We recommend asking for as little information as possible during user registration, and to only prompt new users for additional information after the registration process has been completed.

Some Websites, however, do want to ask for additional information in the registration form itself.

Flask-User (v0.4.5 and up) has the capability to store extra registration fields in the User or the UserProfile records.

Extra registration fields in the User data-model

Extra fields must be defined in the User data-model:

class User(db.Model, UserMixin):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    is_enabled = db.Column(db.Boolean(), nullable=False, default=False)
    email = db.Column(db.String(255), nullable=False, default='')
    password = db.Column(db.String(255), nullable=False, default='')
    # Extra data-model fields
    first_name = db.Column(db.String(50), nullable=False, default='')
    last_name  = db.Column(db.String(50), nullable=False, default='')

    def is_active(self):
      return self.is_enabled

db_adapter = SQLAlchemyAdapter(db, UserClass=User)

A custom RegisterForm must be defined with field names exactly matching the names of the data-model fields:

class MyRegisterForm(RegisterForm):
    first_name = StringField('First name', validators=[DataRequired('First name is required')])
    last_name  = StringField('Last name',  validators=[DataRequired('Last name is required')])

user_manager = UserManager(db_adapter, app, register_form=MyRegisterForm)

A custom templates/flask_user/register.html file must be copied and defined with the extra fields. See Form Templates.

When a new user submits the Register form, Flask-User examines the field names of the form and the User data-model. For each matching field name, the form field value will be stored in the corresponding User field.

See Github repository; example_apps/register_form_app

Extra registration fields in UserProfile data-model

  • Add extra fields to the User data-model
  • Extend a custom MyRegisterForm class from the built-in flask_user.forms.RegisterForm class.
  • Add extra fields to the form using identical field names.
  • Specify your custom registration form: user_manager = UserManager(db_adapter, app, register_form=MyRegisterForm)
  • Copy the built-in templates/flask_user/register.html to your application’s templates/flask_user directory.
  • Add the extra form fields to register.html

Form Templates

Forms are generated using Flask Jinja2 template files. Flask will first look for template files in the application’s templates directory before looking in Flask-User’s templates directory.

Forms can thus be customized by copying the built-in Form template files from the Flask-User directory to your application’s directory and editing the new copy.

Flask-User typically installs in the flask_user sub-directory of the Python packages directory. The location of this directory depends on Python, virtualenv and pip and can be determined with the following command:

python -c "from distutils.sysconfig import get_python_lib; print get_python_lib();"

Let’s assume that:

  • The Python packages dir is: ~/.virtualenvs/ENVNAME/lib/python2.7/site-packages/
  • The Flask-User dir is: ~/.virtualenvs/ENVNAME/lib/python2.7/site-packages/flask_user/
  • Your app directory is: ~/path/to/YOURAPP/YOURAPP (your application directory typically contains the ‘static’ and ‘templates’ sub-directories).

Forms can be customized by copying the form template files like so:

cd ~/path/to/YOURAPP/YOURAPP
mkdir -p templates/flask_user
cp ~/.virtualenvs/ENVNAME/lib/python2.7/site-packages/flask_user/templates/flask_user/*.html templates/flask_user/.

and by editing the copies to your liking.

The following form template files resides in the templates directory and can be customized:

base.html                             # root template

flask_user/_authorized_base.html           # extends base.html
flask_user/change_password.html       # extends flask_user/_authorized_base.html
flask_user/change_username.html       # extends flask_user/_authorized_base.html
flask_user/manage_emails.html         # extends flask_user/_authorized_base.html
flask_user/edit_user_profile.html          # extends flask_user/_authorized_base.html

flask_user/_public_base.html           # extends base.html
flask_user/forgot_password.html       # extends flask_user/_public_base.html
flask_user/login.html                 # extends flask_user/_public_base.html
flask_user/login_or_register.html     # extends flask_user/_public_base.html
flask_user/register.html              # extends flask_user/_public_base.html
flask_user/request_email_confirmation.html  # extends flask_user/_public_base.html
flask_user/reset_password.html        # extends flask_user/_public_base.html

If you’d like the Login form and the Register form to appear on one page, you can use the following application config settings:

# Place the Login form and the Register form on one page:
# Only works for Flask-User v0.4.9 and up
USER_LOGIN_TEMPLATE                     = 'flask_user/login_or_register.html'
USER_REGISTER_TEMPLATE                  = 'flask_user/login_or_register.html'

Password and Username Validators

Flask-User comes standard with a password validator (at least 6 chars, 1 upper case letter, 1 lower case letter, 1 digit) and with a username validator (at least 3 characters in “abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._”).

Custom validators can be specified by setting an property on the Flask-User’s UserManager object:

from wtforms.validators import ValidationError

def my_password_validator(form, field):
    password = field.data
    if len(password) < 8:
        raise ValidationError(_('Password must have at least 8 characters'))

def my_username_validator(form, field):
    username = field.data
    if len(username) < 4:
        raise ValidationError(_('Username must be at least 4 characters long'))
    if not username.isalnum():
        raise ValidationError(_('Username may only contain letters and numbers'))

user_manager = UserManager(db_adapter,
        password_validator=my_password_validator,
        username_validator=my_username_validator)
user_manager.init_app(app)

Password hashing

To hash a password, Flask-User:

  • calls user_manager.hash_password(),
  • which calls user_manager.password_crypt_context,
  • which is initialized to CryptContext(schemes=[app.config['USER_PASSWORD_HASH']]),
  • where USER_PASSWORD_HASH = 'bcrypt'.

See http://pythonhosted.org/passlib/new_app_quickstart.html

Developers can customize the password hashing in the following ways:

By changing an application config setting:

USER_PASSWORD_HASH = 'sha512_crypt'

By changing the crypt_context:

my_password_crypt_context = CryptContext(
        schemes=['bcrypt', 'sha512_crypt', 'pbkdf2_sha512'])
user_manager = UserManager(db_adapter, app,
        password_crypt_context=my_password_crypt_context)

By sub-classing hash_password():

class MyUserManager(UserManager):
    def hash_password(self, password):
        return self.password

    def verify_password(self, password, password_hash)
        return self.hash_password(password) == password_hash

Backward compatibility with Flask-Security

Flask-Security performs a SHA512 HMAC prior to calling passlib. To continue using passwords that have been generated with Flask-Security, add the following settings to your application config:

# Keep the following Flaks and Flask-Security settings the same
SECRET_KEY = ...
SECURITY_PASSWORD_HASH = ...
SECURITY_PASSWORD_SALT = ...

# Set Flask-Security backward compatibility mode
USER_PASSWORD_HASH_MODE = 'Flask-Security'
USER_PASSWORD_HASH      = SECURITY_PASSWORD_HASH
USER_PASSWORD_SALT      = SECURITY_PASSWORD_SALT

View Functions

The built-in View Functions contain considerable business logic, so we recommend first trying the approach of Form Templates before making use of customized View Functions.

Custom view functions are specified by setting an property on the Flask-User’s UserManager object:

# View functions
user_manager = UserManager(db_adapter,
        change_password_view_function      = my_view_function1,
        change_username_view_function      = my_view_function2,
        confirm_email_view_function        = my_view_function3,
        email_action_view_function         = my_view_function4,
        forgot_password_view_function      = my_view_function5,
        login_view_function                = my_view_function6,
        logout_view_function               = my_view_function7,
        manage_emails_view_function        = my_view_function8,
        register_view_function             = my_view_function9,
        resend_email_confirmation_view_function = my_view_function10,
        reset_password_view_function       = my_view_function11,
        )
user_manager.init_app(app)

Token Generation

To be documented.

Emails

Emails are generated using Flask Jinja2 template files. Flask will first look for template files in the application’s templates directory before looking in Flask-User’s templates directory.

Emails can thus be customized by copying the built-in Email template files from the Flask-User directory to your application’s directory and editing the new copy.

Flask-User typically installs in the flask_user sub-directory of the Python packages directory. The location of this directory depends on Python, virtualenv and pip and can be determined with the following command:

python -c "from distutils.sysconfig import get_python_lib; print get_python_lib();"

Let’s assume that:

  • The Python packages dir is: ~/.virtualenvs/ENVNAME/lib/python2.7/site-packages/
  • The Flask-User dir is: ~/.virtualenvs/ENVNAME/lib/python2.7/site-packages/flask_user/
  • Your app directory is: ~/path/to/YOURAPP/YOURAPP (your application directory typically contains the ‘static’ and ‘templates’ sub-directories).

The built-in Email template files can be copied like so:

cd ~/path/to/YOURAPP/YOURAPP
mkdir -p templates/flask_user/emails
cp ~/.virtualenvs/ENVNAME/lib/python2.7/site-packages/flask_user/templates/flask_user/emails/* templates/flask_user/emails/.

Each email type has three email template files. The ‘registered’ email for example has the following files:

templates/flask_user/emails/registered_subject.txt   # The email subject line
templates/flask_user/emails/registered_message.html  # The email message in HTML format
templates/flask_user/emails/registered_message.txt   # The email message in Text format

Each file is extended from the base template file:

templates/flask_user/emails/base_subject.txt
templates/flask_user/emails/base_message.html
templates/flask_user/emails/base_message.txt

The base template files are used to define email elements that are similar in all types of email messages.

If, for example, for every email you want to:
- Set the background color and padding,
- Start with a logo and salutation, and
- End with a signature,
you can define templates/flask_user/emails/base_message.html like so
<div style="background-color: #f4f2dd; padding: 10px;">
    <p><img src="http://example.com/static/images/email-logo.png"></p>
    <p>Dear Customer,</p>
    {% block message %}{% endblock %}
    <p>Sincerely,<br/>
    The Flask-User Team</p>
</div>

and define the confirmation specific messages in templates/flask_user/emails/confirm_email_message.html like so:

{% extends "flask_user/emails/base_message.html" %}

{% block message %}
<p>Thank you for registering with Flask-User.</p>
<p>Visit the link below to complete your registration:</p>
<p><a href="{{ confirm_email_link }}">Confirm your email address</a>.</p>
<p>If you did not initiate this registration, you may safely ignore this email.</p>
{% endblock %}

SQLDbAdapter

Flask-User uses SQLDbAdapter and installs Flask-SQLAlchemy by default. No customization is required to work with SQL databases.

Configure the SQLALCHEMY_DATABASE_URI setting in your app config to point to the desired server and database.


MongoDbAdapter

Flask-User ships with a MongoDbAdapter, but Flask-MongoEngine must be installed manually:

pip install Flask-MongeEngine

and minor customization is required to use and configure the MongoDbAdapter:

# Setup Flask-MongoEngine
from Flask-MongoEngine import MongoEngine
db = MongoEngine(app)

# Customize Flask-User
class CustomUserManager(UserManager):

    def customize(self, app):
        # Use the provided MongoDbAdapter
        from flask_user.db_adapters import MongoDbAdapter
        self.db_adapter = MongoDbAdapter(app, db)

# Define the User document
# NB: Make sure to add flask_user UserMixin !!!
class User(db.Document, UserMixin):

    # User authentication information
    username = db.StringField(default='')
    email = db.StringField(default='')
    password = db.StringField()
    email_confirmed_at = db.DateTimeField(default=None)

    # User information
    first_name = db.StringField(default='')
    last_name = db.StringField(default='')

    # Relationships
    roles = db.ListField(db.StringField(), default=[])

# Setup Flask-User
user_manager = CustomUserManager(app, db, User)

Configure the MONGODB_SETTINGS setting in your app config to point to the desired server and database.


SMTPEmailAdapter

Flask-User uses the SMTPEmailAdapter and install Flask-Mail by default. No customization is required to use SMTPEmailAdapter to send emails via SMTP.

Configure the MAIL_... settings in your app config to point to the desired SMTP server and account.


SendmailEmailAdapter

Flask-User ships with a SendmailEmailAdapter, but Flask-Sendmail must be installed manually:

pip install Flask-Sendmail

and minor customization is required use to SendmailEmailAdapter to send emails via sendmail.:

# Customize Flask-User
class CustomUserManager(UserManager):

    def customize(self, app):
        # Use the provided SendmailEmailAdapter
        from flask_user.email_adapters import SendmailEmailAdapter
        self.email_adapter = SendmailEmailAdapter(app)

# Setup Flask-User
user_manager = CustomUserManager(app, db, User)

No configuration is required (other than setting up sendmail on your system).


SendgridEmailAdapter

Flask-User ships with a SendgridEmailAdapter, but sendgrid-python needs to be installed manually:

pip install sendgrid

and minor customization is required to use SendgridEmailAdapter to send emais via SendGrid:

# Customize Flask-User
class CustomUserManager(UserManager):

    def customize(self, app):
        # Use the provided SendgridEmailAdapter
        from flask_user.email_adapters import SendgridEmailAdapter
        self.email_adapter = SendgridEmailAdapter(app)

# Setup Flask-User
user_manager = CustomUserManager(app, db, User)

Configuration: TBD.