FLASK-SECURITY(1) Flask-Security FLASK-SECURITY(1) NAME flask-security - Flask-Security 5.4.3 Flask-Security: add a drop of security to your Flask application. Flask-Security allows you to quickly add common security mechanisms to your Flask application. They include: 1. Session based authentication 2. Role and Permission management 3. Password hashing 4. Basic HTTP authentication 5. Token based authentication 6. Token based account activation (optional) 7. Token based password recovery / resetting (optional) 8. Two-factor authentication (optional) 9. Unified sign in (optional) 10. User registration (optional) 11. Login tracking (optional) 12. JSON/Ajax Support 13. WebAuthn Support (optional) 14. Use 'social'/Oauth for authentication (e.g. google, github, ..) (optional) Many of these features are made possible by integrating various Flask extensions and libraries. They include: o Flask-Login o Flask-Mailman o Flask-Principal o Flask-WTF o itsdangerous o passlib o QRCode o webauthn o authlib Additionally, it assumes you'll be using a common library for your database connections and model definitions. Flask-Security supports the following Flask extensions out of the box for data persistence: 1. Flask-SQLAlchemy 2. MongoEngine 3. Peewee Flask utils 4. PonyORM - NOTE: not currently working - Help needed!. 5. SQLAlchemy sessions GETTING STARTED Installation Installing Flask-Security-Too using: pip install flask-security-too will install the basic package along with its required dependencies: o Flask o Flask-Login o Flask-Principal o Flask-WTF o email-validator o itsdangerous o passlib o Blinker These are not sufficient for a complete application - other packages are required based on features desired, password hash algorithms, storage backend, etc. Flask-Security-Too has additional distribution 'extras' that can reduce the hassle of figuring out all the required packages. You can install these using the standard pip syntax: pip install flask-security-too[extra1,extra2, ...] Supported extras are: o babel - Translation services. It will install babel and Flask-Babel. o fsqla - Use flask-sqlalchemy and sqlalchemy as your storage interface. o common - Install Flask-Mailman, bcrypt (the default password hash), and bleach. o mfa - Install packages used for multi-factor (two-factor, unified signin, WebAuthn): cryptography, qrcode, phonenumberslite (note that for SMS you still need to pick an SMS provider and install appropriate packages), and webauthn. Your application will also need a database backend: o Sqlite is supported out of the box. o For PostgreSQL install psycopg2. o For MySQL install pymysql. o For MongoDB install Mongoengine. For additional details on configuring your database engine connector - refer to sqlalchemy_engine Quick Start There are some complete (but simple) examples available in the examples directory of the Flask-Security repo. NOTE: The below quickstarts are just that - they don't enable most of the features (such as registration, reset, etc.). They basically create a single user, and you can login as that user... that's it. As you add more features, additional packages (e.g. Flask-Mailman, Flask-Babel, qrcode) might be required and will need to be added to your requirements.txt (or equivalent) file. Flask-Security does some configuration validation and will output error messages to the console for some missing packages. NOTE: The default SECURITY_PASSWORD_HASH is "bcrypt" - so be sure to install bcrypt. If you opt for a different hash e.g. "argon2" you will need to install the appropriate package e.g. argon_cffi. DANGER: The examples below place secrets in source files. Never do this for your application especially if your source code is placed in a public repo. How you pass in secrets securely will depend on your deployment model - however in most cases (e.g. docker, lambda) using environment variables will be the easiest. o Basic SQLAlchemy Application o Basic SQLAlchemy Application with session o Basic MongoEngine Application o Basic Peewee Application o Mail Configuration o Proxy Configuration o Unit Testing Your Application Basic SQLAlchemy Application SQLAlchemy Install requirements $ python3 -m venv pymyenv $ . pymyenv/bin/activate $ pip install flask-security-too[fsqla,common] SQLAlchemy Application The following code sample illustrates how to get started as quickly as possible using Flask-SQLAlchemy and the built-in model mixins: import os from flask import Flask, render_template_string from flask_sqlalchemy import SQLAlchemy from flask_security import Security, SQLAlchemyUserDatastore, auth_required, hash_password from flask_security.models import fsqla_v3 as fsqla # Create app app = Flask(__name__) app.config['DEBUG'] = True # Generate a nice key using secrets.token_urlsafe() app.config['SECRET_KEY'] = os.environ.get("SECRET_KEY", 'pf9Wkove4IKEAXvy-cQkeDPhv9Cb3Ag-wyJILbq_dFw') # Bcrypt is set as default SECURITY_PASSWORD_HASH, which requires a salt # Generate a good salt using: secrets.SystemRandom().getrandbits(128) app.config['SECURITY_PASSWORD_SALT'] = os.environ.get("SECURITY_PASSWORD_SALT", '146585145368132386173505678016728509634') # have session and remember cookie be samesite (flask/flask_login) app.config["REMEMBER_COOKIE_SAMESITE"] = "strict" app.config["SESSION_COOKIE_SAMESITE"] = "strict" # Use an in-memory db app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' # As of Flask-SQLAlchemy 2.4.0 it is easy to pass in options directly to the # underlying engine. This option makes sure that DB connections from the # pool are still valid. Important for entire application since # many DBaaS options automatically close idle connections. app.config["SQLALCHEMY_ENGINE_OPTIONS"] = { "pool_pre_ping": True, } app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False # Create database connection object db = SQLAlchemy(app) # Define models fsqla.FsModels.set_db_info(db) class Role(db.Model, fsqla.FsRoleMixin): pass class User(db.Model, fsqla.FsUserMixin): pass # Setup Flask-Security user_datastore = SQLAlchemyUserDatastore(db, User, Role) app.security = Security(app, user_datastore) # Views @app.route("/") @auth_required() def home(): return render_template_string("Hello {{ current_user.email }}") # one time setup with app.app_context(): # Create User to test with db.create_all() if not app.security.datastore.find_user(email="test@me.com"): app.security.datastore.create_user(email="test@me.com", password=hash_password("password")) db.session.commit() if __name__ == '__main__': app.run() You can run this either with: flask run or: python app.py Basic SQLAlchemy Application with session SQLAlchemy Install requirements $ python3 -m venv pymyenv $ . pymyenv/bin/activate $ pip install flask-security-too[common] sqlalchemy SQLAlchemy Application (w/o Flask-SQLAlchemy) The following code sample illustrates how to get started as quickly as possible using SQLAlchemy in a declarative way: This example shows how to split your application into 3 files: app.py, database.py and models.py. o app.py import os from flask import Flask, render_template_string from flask_security import Security, current_user, auth_required, hash_password, \ SQLAlchemySessionUserDatastore, permissions_accepted from database import db_session, init_db from models import User, Role # Create app app = Flask(__name__) app.config['DEBUG'] = True # Generate a nice key using secrets.token_urlsafe() app.config['SECRET_KEY'] = os.environ.get("SECRET_KEY", 'pf9Wkove4IKEAXvy-cQkeDPhv9Cb3Ag-wyJILbq_dFw') # Bcrypt is set as default SECURITY_PASSWORD_HASH, which requires a salt # Generate a good salt using: secrets.SystemRandom().getrandbits(128) app.config['SECURITY_PASSWORD_SALT'] = os.environ.get("SECURITY_PASSWORD_SALT", '146585145368132386173505678016728509634') # Don't worry if email has findable domain app.config["SECURITY_EMAIL_VALIDATOR_ARGS"] = {"check_deliverability": False} # manage sessions per request - make sure connections are closed and returned app.teardown_appcontext(lambda exc: db_session.close()) # Setup Flask-Security user_datastore = SQLAlchemySessionUserDatastore(db_session, User, Role) app.security = Security(app, user_datastore) # Views @app.route("/") @auth_required() def home(): return render_template_string('Hello {{current_user.email}}!') @app.route("/user") @auth_required() @permissions_accepted("user-read") def user_home(): return render_template_string("Hello {{ current_user.email }} you are a user!") # one time setup with app.app_context(): init_db() # Create a user and role to test with app.security.datastore.find_or_create_role( name="user", permissions={"user-read", "user-write"} ) db_session.commit() if not app.security.datastore.find_user(email="test@me.com"): app.security.datastore.create_user(email="test@me.com", password=hash_password("password"), roles=["user"]) db_session.commit() if __name__ == '__main__': # run application (can also use flask run) app.run() o database.py from sqlalchemy import create_engine from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.ext.declarative import declarative_base engine = create_engine('sqlite:////tmp/test.db') db_session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine)) Base = declarative_base() Base.query = db_session.query_property() def init_db(): # import all modules here that might define models so that # they will be registered properly on the metadata. Otherwise # you will have to import them first before calling init_db() import models Base.metadata.create_all(bind=engine) o models.py from database import Base from flask_security import UserMixin, RoleMixin, AsaList from sqlalchemy.orm import relationship, backref from sqlalchemy.ext.mutable import MutableList from sqlalchemy import Boolean, DateTime, Column, Integer, \ String, ForeignKey class RolesUsers(Base): __tablename__ = 'roles_users' id = Column(Integer(), primary_key=True) user_id = Column('user_id', Integer(), ForeignKey('user.id')) role_id = Column('role_id', Integer(), ForeignKey('role.id')) class Role(Base, RoleMixin): __tablename__ = 'role' id = Column(Integer(), primary_key=True) name = Column(String(80), unique=True) description = Column(String(255)) permissions = Column(MutableList.as_mutable(AsaList()), nullable=True) class User(Base, UserMixin): __tablename__ = 'user' id = Column(Integer, primary_key=True) email = Column(String(255), unique=True) username = Column(String(255), unique=True, nullable=True) password = Column(String(255), nullable=False) last_login_at = Column(DateTime()) current_login_at = Column(DateTime()) last_login_ip = Column(String(100)) current_login_ip = Column(String(100)) login_count = Column(Integer) active = Column(Boolean()) fs_uniquifier = Column(String(64), unique=True, nullable=False) confirmed_at = Column(DateTime()) roles = relationship('Role', secondary='roles_users', backref=backref('users', lazy='dynamic')) You can run this either with: flask run or: python app.py Basic MongoEngine Application MongoEngine Install requirements $ python3 -m venv pymyenv $ . pymyenv/bin/activate $ pip install flask-security-too[common] mongoengine MongoEngine Application The following code sample illustrates how to get started as quickly as possible using MongoEngine (of course you have to install and start up a local MongoDB instance): import os from flask import Flask, render_template_string from mongoengine import Document, connect from mongoengine.fields import ( BinaryField, BooleanField, DateTimeField, IntField, ListField, ReferenceField, StringField, ) from flask_security import Security, MongoEngineUserDatastore, \ UserMixin, RoleMixin, auth_required, hash_password, permissions_accepted # Create app app = Flask(__name__) app.config['DEBUG'] = True # Generate a nice key using secrets.token_urlsafe() app.config['SECRET_KEY'] = os.environ.get("SECRET_KEY", 'pf9Wkove4IKEAXvy-cQkeDPhv9Cb3Ag-wyJILbq_dFw') # Bcrypt is set as default SECURITY_PASSWORD_HASH, which requires a salt # Generate a good salt using: secrets.SystemRandom().getrandbits(128) app.config['SECURITY_PASSWORD_SALT'] = os.environ.get("SECURITY_PASSWORD_SALT", '146585145368132386173505678016728509634') # Don't worry if email has findable domain app.config["SECURITY_EMAIL_VALIDATOR_ARGS"] = {"check_deliverability": False} # Create database connection object db_name = "mydatabase" db = connect(alias=db_name, db=db_name, host="mongodb://localhost", port=27017) class Role(Document, RoleMixin): name = StringField(max_length=80, unique=True) description = StringField(max_length=255) permissions = ListField(required=False) meta = {"db_alias": db_name} class User(Document, UserMixin): email = StringField(max_length=255, unique=True) password = StringField(max_length=255) active = BooleanField(default=True) fs_uniquifier = StringField(max_length=64, unique=True) confirmed_at = DateTimeField() roles = ListField(ReferenceField(Role), default=[]) meta = {"db_alias": db_name} # Setup Flask-Security user_datastore = MongoEngineUserDatastore(db, User, Role) app.security = Security(app, user_datastore) # Views @app.route("/") @auth_required() def home(): return render_template_string("Hello {{ current_user.email }}") @app.route("/user") @auth_required() @permissions_accepted("user-read") def user_home(): return render_template_string("Hello {{ current_user.email }} you are a user!") # one time setup with app.app_context(): # Create a user and role to test with app.security.datastore.find_or_create_role( name="user", permissions={"user-read", "user-write"} ) if not app.security.datastore.find_user(email="test@me.com"): app.security.datastore.create_user(email="test@me.com", password=hash_password("password"), roles=["user"]) if __name__ == '__main__': # run application (can also use flask run) app.run() Basic Peewee Application Peewee Install requirements $ python3 -m venv pymyenv $ . pymyenv/bin/activate $ pip install flask-security-too[common] peewee Peewee Application The following code sample illustrates how to get started as quickly as possible using Peewee: import os from flask import Flask, render_template_string from playhouse.flask_utils import FlaskDB from peewee import * from flask_security import Security, PeeweeUserDatastore, \ UserMixin, RoleMixin, auth_required, hash_password # Create app app = Flask(__name__) app.config['DEBUG'] = True # Generate a nice key using secrets.token_urlsafe() app.config['SECRET_KEY'] = os.environ.get("SECRET_KEY", 'pf9Wkove4IKEAXvy-cQkeDPhv9Cb3Ag-wyJILbq_dFw') # Bcrypt is set as default SECURITY_PASSWORD_HASH, which requires a salt # Generate a good salt using: secrets.SystemRandom().getrandbits(128) app.config['SECURITY_PASSWORD_SALT'] = os.environ.get("SECURITY_PASSWORD_SALT", '146585145368132386173505678016728509634') app.config['DATABASE'] = { 'name': 'example.db', 'engine': 'peewee.SqliteDatabase', } # Create database connection object db = FlaskDB(app) class Role(RoleMixin, db.Model): name = CharField(unique=True) description = TextField(null=True) permissions = TextField(null=True) # N.B. order is important since db.Model also contains a get_id() - # we need the one from UserMixin. class User(UserMixin, db.Model): email = TextField() password = TextField() active = BooleanField(default=True) fs_uniquifier = TextField(null=False) confirmed_at = DateTimeField(null=True) class UserRoles(db.Model): # Because peewee does not come with built-in many-to-many # relationships, we need this intermediary class to link # user to roles. user = ForeignKeyField(User, related_name='roles') role = ForeignKeyField(Role, related_name='users') name = property(lambda self: self.role.name) description = property(lambda self: self.role.description) def get_permissions(self): return self.role.get_permissions() # Setup Flask-Security user_datastore = PeeweeUserDatastore(db, User, Role, UserRoles) app.security = Security(app, user_datastore) # Views @app.route('/') @auth_required() def home(): return render_template_string("Hello {{ current_user.email }}") # one time setup with app.app_context(): # Create a user to test with for Model in (Role, User, UserRoles): Model.drop_table(fail_silently=True) Model.create_table(fail_silently=True) if not app.security.datastore.find_user(email="test@me.com"): app.security.datastore.create_user(email="test@me.com", password=hash_password("password")) if __name__ == '__main__': app.run() Mail Configuration Flask-Security integrates with an outgoing mail service via the mail_util_cls which is part of initial configuration. The default class flask_security.MailUtil utilizes the Flask-Mailman package. Be sure to add flask_mailman to your requirements.txt. The older and no longer maintained package Flask-Mail is also (still) supported. The following code illustrates a basic setup, which could be added to the basic application code in the previous section: # At top of file from flask_mailman import Mail # After 'Create app' app.config['MAIL_SERVER'] = 'smtp.example.com' app.config['MAIL_PORT'] = 587 app.config['MAIL_USE_TLS'] = True app.config['MAIL_USERNAME'] = 'username' app.config['MAIL_PASSWORD'] = 'password' mail = Mail(app) To learn more about the various Flask-Mailman settings to configure it to work with your particular email server configuration, please see the Flask-Mailman documentation. Proxy Configuration The user tracking features need an additional configuration in HTTP proxy environment. The following code illustrates a setup with a single HTTP proxy in front of the web application: # At top of file from werkzeug.middleware.proxy_fix import ProxyFix # After 'Create app' app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1) To learn more about the ProxyFix middleware, please see the Werkzeug documentation. Unit Testing Your Application As soon as you add any of the Flask-Security decorators to your API endpoints, it can be frustrating to unit test your basic routing (and roles and permissions). Without getting into the argument of the difference between unit tests and integration tests - you can approach testing in 2 ways: o 'Pure' unit test - mocking out all lower level objects (such as the data store) o Complete app with in-memory/temporary DB (with little or no mocking). Look in the Flask-Security repo examples directory for actual code that implements the second approach which is much simpler and with an in-memory DB fairly fast. You also might want to set the following configurations in your conftest.py: app.config["WTF_CSRF_ENABLED"] = False # Our test emails/domain isn't necessarily valid app.config["SECURITY_EMAIL_VALIDATOR_ARGS"] = {"check_deliverability": False} # Make this plaintext for most tests - reduces unit test time by 50% app.config["SECURITY_PASSWORD_HASH"] = "plaintext" Features Flask-Security allows you to quickly add common security mechanisms to your Flask application. They include: Session Based Authentication Session based authentication is fulfilled entirely by the Flask-Login extension. Flask-Security handles the configuration of Flask-Login automatically based on a few of its own configuration values and uses Flask-Login's alternative token feature to associate the value of fs_uniquifier with the user. (This enables easily invalidating all existing sessions for a given user without having to change their user id). Flask-WTF integrates with the session as well to provide out of the box CSRF support. Flask-Security extends that to support configurations that would require CSRF for requests that are authenticated via session cookies, but not for requests authenticated using tokens. Role/Identity Based Access Flask-Security implements very basic role management out of the box. This means that you can associate a high level role or multiple roles to any user. For instance, you may assign roles such as Admin, Editor, SuperUser, or a combination of said roles to a user. Access control is based on the role name and/or permissions contained within the role; and all roles should be uniquely named. This feature is implemented using the Flask-Principal extension. As with basic RBAC, permissions can be assigned to roles to provide more granular access control. Permissions can be associated with one or more roles (the RoleModel contains a list of permissions). The values of permissions are completely up to the developer - Flask-Security simply treats them as strings. If you'd like to implement even more granular access control (such as per-object), you can refer to the Flask-Principal documentation on this topic. Password Hashing Password hashing is enabled with passlib. Passwords are hashed with the bcrypt function by default but you can easily configure the hashing algorithm. You should always use a hashing algorithm in your production environment. Hash algorithms not listed in SECURITY_PASSWORD_SINGLE_HASH will be double hashed - first an HMAC will be computed, then the selected hash function will be used. In this case - you must provide a SECURITY_PASSWORD_SALT. A good way to generate this is: secrets.SystemRandom().getrandbits(128) Bear in mind passlib does not assume which algorithm you will choose and may require additional libraries to be installed. Password Validation and Complexity Consult Password Validation and Complexity. Basic HTTP Authentication Basic HTTP authentication is achievable using a simple view method decorator. This feature expects the incoming authentication information to identify a user in the system. This means that the username must be equal to their email address. Token Authentication Token based authentication can be used by retrieving the user auth token from an authentication endpoint (e.g. /login, /us-signin, /wan-signin). Perform an HTTP POST with a query param of include_auth_token and the authentication details as JSON data. A successful call will return the authentication token. This token can be used in subsequent requests to protected resources. The auth token should be supplied in the request through an HTTP header or query string parameter. By default the HTTP header name is Authentication-Token and the default query string parameter name is auth_token. Authentication tokens are generated using a uniquifier field in the user's UserModel. By default that field is fs_uniquifier. This means that if that field is changed (via UserDatastore.set_uniquifier()) then any existing authentication tokens will no longer be valid. This value is changed whenever a user changes their password. If this is not the desired behavior then you can add an additional attribute to the UserModel: fs_token_uniquifier and that will be used instead, thus isolating password changes from authentication tokens. That attribute can be changed via UserDatastore.set_token_uniquifier(). This attribute should have unique=True. Unlike fs_uniquifier, it can be set to nullable - it will automatically be generated at first use if null. Authentication tokens have 2 options for specifying expiry time SECURITY_TOKEN_MAX_AGE is applied to ALL authentication tokens. Each authentication token can itself have an embedded expiry value (settable via the SECURITY_TOKEN_EXPIRE_TIMESTAMP callable). NOTE: While every Flask-Security endpoint will accept an authentication token header, there are some endpoints that require session information (e.g. a session cookie). Please read Freshness and CSRF Two-factor Authentication Two-factor authentication is enabled by generating time-based one time passwords (Tokens). The tokens are generated using the users totp secret, which is unique per user, and is generated both on first login, and when changing the two-factor method (doing this causes the previous totp secret to become invalid). The token is provided by one of 3 methods - email, sms (service is not provided), or an authenticator app such as Google Authenticator, LastPass Authenticator, or Authy. By default, tokens provided by the authenticator app are valid for 2 minutes, tokens sent by mail for up to 5 minute and tokens sent by sms for up to 2 minutes. The QR code used to supply the authenticator app with the secret is generated using the qrcode library. Please read Theory of Operation for more details. The Two-factor feature offers the ability for a user to 'rescue' themselves if they lose track of their secondary factor device. Rescue options include sending a one time code via email, send an email to the application admin, and using a previously generated and downloaded one-time code (see SECURITY_MULTI_FACTOR_RECOVERY_CODES). Unified Sign In This feature is in Beta - mostly due to it being brand new and little to no production soak time Unified sign in provides a generalized login endpoint that takes an identity and a passcode; where (based on configuration): o identity is any of SECURITY_USER_IDENTITY_ATTRIBUTES (e.g. email, username, phone) o passcode is a password or a one-time code (delivered via email, SMS, or authenticator app) Please see this Wikipedia article about multi-factor authentication. Using this feature, it is possible to not require the user to have a stored password at all, and just require the use of a one-time code. The mechanisms for generating and delivering the one-time code are similar to common two-factor mechanisms. This one-time code can be configured to be delivered via email, SMS or authenticator app - however be aware that NIST does not recommend email for this purpose (though many web sites do so) due to the fact that a) email may travel through many different servers as part of being delivered - and b) is available from any device. Using SMS or an authenticator app means you are providing "something you have" (the mobile device) and either "something you know" (passcode to unlock your device) or "something you are" (biometric quality to unlock your device). This effectively means that using a one-time code to sign in, is in fact already two-factor (if using SMS or authenticator app). Many large authentication providers already offer this - here is Microsoft's version. Note that by configuring SECURITY_US_ENABLED_METHODS an application can use this endpoint JUST with identity/password or in fact disallow passwords altogether. Unified sign in is integrated with two-factor authentication. Since in general there is no need for a second factor if the initial authentication was with SMS or an authenticator application, the SECURITY_US_MFA_REQUIRED configuration determines which primary authentication mechanisms require a second factor. By default limited to email and password (if two-factor is enabled). Be aware that by default, the SECURITY_US_SETUP_URL endpoint is protected with a freshness check (see flask_security.auth_required()) which means it requires a session cookie to function properly. This is true even if using JSON payload or token authentication. If you disable the freshness check then sessions aren't required. Current Limited Functionality: o Change password does not work if a user registers without a password. However forgot-password will allow the user to set a new password. o Registration and Confirmation only work with email - so while you can enable multiple authentication methods, you still have to register with email. WebAuthn This feature is in Beta - mostly due to it being brand new and little to no production soak time WebAuthn is a standardized protocol that connects authenticators (such as YubiKey and mobile biometrics) with websites. Flask-Security supports using WebAuthn keys as either 'first' or 'secondary' authenticators. Please read WebAuthn for more details. Email Confirmation If desired you can require that new users confirm their email address. Flask-Security will send an email message to any new users with a confirmation link. Upon navigating to the confirmation link, the user's account will be set to 'confirmed'. The user can then sign in usually the normal mechanisms. There is also view for resending a confirmation link to a given email if the user happens to try to use an expired token or has lost the previous email. Confirmation links can be configured to expire after a specified amount of time (default 5 days). Password Reset/Recovery Password reset and recovery is available for when a user forgets their password. Flask-Security sends an email to the user with a link to a view which allows them to reset their password. Once the password is reset they are redirected to the login page where they need to authenticate using the new password. Password reset links can be configured to expire after a specified amount of time. As with password change - this will update the the user's fs_uniquifier attribute which will invalidate all existing sessions AND (by default) all authentication tokens. User Registration Flask-Security comes packaged with a basic user registration view. This view is very simple and new users need only supply an email address and their password. This view can be overridden if your registration process requires more fields. User email is validated and normalized using the email_validator package. The SECURITY_USERNAME_ENABLE configuration option, when set to True, will add support for the user to register a username in addition to an email. By default, the user will be able to authenticate with EITHER email or username - however that can be changed via the SECURITY_USER_IDENTITY_ATTRIBUTES. Password Change Flask-Security comes packaged with a basic change user password view. Unlike password recovery, this endpoint is used when the user is already authenticated. The result of a successful password change is not only a new password, but a new value for fs_uniquifier. This has the effect is immediately invalidating all existing sessions. The change request itself effectively re-logs in the user so a new session is created. Note that since the user is effectively re-logged in, the same signals are sent as when the user normally authenticates. NOTE: The fs_uniquifier by default, controls both sessions and authenticated tokens. Thus changing the password also invalidates all authentication tokens. This may not be desirable behavior, so if the UserModel contains an attribute fs_token_uniquifier, then that will be used when generating authentication tokens and so won't be affected by password changes. Login Tracking Flask-Security can, if configured, keep track of basic login events and statistics. They include: o Last login date o Current login date o Last login IP address o Current login IP address o Total login count JSON/Ajax Support Flask-Security supports JSON/Ajax requests where appropriate. Please look at CSRF for details on how to work with JSON and Single Page Applications. More specifically JSON is supported for the following operations: o Login requests o Unified sign in requests o Registration requests o Change password requests o Confirmation requests o Forgot password requests o Passwordless login requests o Two-factor login requests o Change two-factor method requests o WebAuthn registration and signin requests o Two-Factor recovery code requests In addition, Single-Page-Applications (like those built with Vue, Angular, and React) are supported via customizable redirect links. Note: All registration requests done through JSON/Ajax utilize the confirm_register_form. Command Line Interface Basic Click commands for managing users and roles are automatically registered. They can be completely disabled or their names can be changed. Run flask --help and look for users and roles. Social/Oauth Authentication Flask-Security provides a thin layer which integrates authlib with Flask-Security views and features (such as two-factor authentication). Flask-Security is shipped with support for github and google - others can be added by the application (see loginpass for many examples). See flask_security.OAuthGlue and flask_security.FsOAuthProvider Please note - this is for authentication only, and the authenticating user must already be a registered user in your application. Once authenticated, all further authorization uses Flask-Security role/permission mechanisms. See Flask OAuth Client for details. Note in particular, that you must setup and provide provider specific information - and most importantly - XX_CLIENT_ID and XX_CLIENT_SECRET should be specified as environment variables. We have seen issues with some providers when SESSION_COOKIE_SAMESITE = "strict". The handshake (sometimes just the first time when the user is being asked to accept your application) fails due to the session cookie not getting sent as part of the redirect. A very simple example of configuring social auth with Flask-Security is available in the examples directory. Configuration WARNING: Be sure to set all configuration (in app.config["xxx"]) PRIOR to instantiating the Security class or calling security.init_app(). The following configuration values are used by Flask-Security: Core These configuration keys are used globally across all features. SECRET_KEY This is actually part of Flask - but is used by Flask-Security to sign all tokens. It is critical this is set to a strong value. For python3 consider using: secrets.token_urlsafe() SECURITY_BLUEPRINT_NAME Specifies the name for the Flask-Security blueprint. Default: "security". SECURITY_URL_PREFIX Specifies the URL prefix for the Flask-Security blueprint. Default: None. SECURITY_STATIC_FOLDER Specifies the folder name for static files (webauthn). Default: "static". Added in version 5.1.0. SECURITY_STATIC_FOLDER_URL Specifies the URL for static files used by Flask-Security (webauthn). See Flask documentation https://flask.palletsprojects.com/en/latest/blueprints/#static-files Default: "/fs-static". Added in version 5.1.0. SECURITY_SUBDOMAIN Specifies the subdomain for the Flask-Security blueprint. If your authenticated content is on a different subdomain, also enable SECURITY_REDIRECT_ALLOW_SUBDOMAINS. Default: None. SECURITY_FLASH_MESSAGES Specifies whether or not to flash messages during security procedures. Default: True. SECURITY_I18N_DOMAIN Specifies the name for domain used for translations. Default: "flask_security". SECURITY_I18N_DIRNAME Specifies the directory containing the MO files used for translations. When using flask-babel this can also be a list of directory names - this enables application to override a subset of messages if desired. The default builtin uses translations shipped with Flask-Security. Default: "builtin". Changed in version 5.2.0: "builtin" is a special name which will be interpreted as the translations directory within the installation of Flask-Security. SECURITY_PASSWORD_HASH Specifies the password hash algorithm to use when hashing passwords. Recommended values for production systems are bcrypt, argon2, sha512_crypt, or pbkdf2_sha512. Some algorithms require the installation of a backend package (e.g. bcrypt, argon2). Default: "bcrypt". SECURITY_PASSWORD_SCHEMES List of support password hash algorithms. SECURITY_PASSWORD_HASH must be from this list. Passwords encrypted with any of these schemes will be honored. SECURITY_DEPRECATED_PASSWORD_SCHEMES List of password hash algorithms that are considered weak and will be accepted, however on first use, will be re-hashed to the current setting of SECURITY_PASSWORD_HASH. Default: ["auto"] which means any password found that wasn't hashed using SECURITY_PASSWORD_HASH will be re-hashed. SECURITY_PASSWORD_SALT Specifies the HMAC salt. This is required for all schemes that are configured for double hashing. A good salt can be generated using: secrets.SystemRandom().getrandbits(128). Default: None. SECURITY_PASSWORD_SINGLE_HASH A list of schemes that should not be hashed twice. By default, passwords are hashed twice, first with SECURITY_PASSWORD_SALT, and then with a random salt. Default: a list of known schemes not working with double hashing (django_{digest}, plaintext). SECURITY_HASHING_SCHEMES List of algorithms used for encrypting/hashing sensitive data within a token (Such as is sent with confirmation or reset password). Default: ["sha256_crypt", "hex_md5"]. SECURITY_DEPRECATED_HASHING_SCHEMES List of deprecated algorithms used for creating and validating tokens. Default: ["hex_md5"]. SECURITY_PASSWORD_HASH_OPTIONS Specifies additional options to be passed to the hashing method. This is deprecated as of passlib 1.7. Deprecated since version 3.4.0: see: SECURITY_PASSWORD_HASH_PASSLIB_OPTIONS SECURITY_PASSWORD_HASH_PASSLIB_OPTIONS Pass additional options to the various hashing methods. This is a dict of the form {__