'\" t .\" Man page generated from reStructuredText. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "FLASK-SECURITY" "1" "Oct 06, 2024" "5.4.x" "Flask-Security" .SH NAME flask-security \- Flask-Security 5.4.3 \X'tty: link https://github.com/Flask-Middleware/flask-security'\fI\%Flask\-Security: add a drop of security to your Flask application.\fP\X'tty: link' .sp Flask\-Security allows you to quickly add common security mechanisms to your Flask application. They include: .INDENT 0.0 .IP 1. 4 Session based authentication .IP 2. 4 Role and Permission management .IP 3. 4 Password hashing .IP 4. 4 Basic HTTP authentication .IP 5. 4 Token based authentication .IP 6. 4 Token based account activation (optional) .IP 7. 4 Token based password recovery / resetting (optional) .IP 8. 4 Two\-factor authentication (optional) .IP 9. 4 Unified sign in (optional) .IP 10. 4 User registration (optional) .IP 11. 4 Login tracking (optional) .IP 12. 4 JSON/Ajax Support .IP 13. 4 WebAuthn Support (optional) .IP 14. 4 Use \(aqsocial\(aq/Oauth for authentication (e.g. google, github, ..) (optional) .UNINDENT .sp Many of these features are made possible by integrating various Flask extensions and libraries. They include: .INDENT 0.0 .IP \(bu 2 \X'tty: link https://flask-login.readthedocs.org/en/latest/'\fI\%Flask\-Login\fP\X'tty: link' .IP \(bu 2 \X'tty: link https://pypi.org/project/Flask-Mailman/'\fI\%Flask\-Mailman\fP\X'tty: link' .IP \(bu 2 \X'tty: link https://pypi.org/project/Flask-Principal/'\fI\%Flask\-Principal\fP\X'tty: link' .IP \(bu 2 \X'tty: link https://pypi.org/project/Flask-WTF/'\fI\%Flask\-WTF\fP\X'tty: link' .IP \(bu 2 \X'tty: link https://pypi.org/project/itsdangerous/'\fI\%itsdangerous\fP\X'tty: link' .IP \(bu 2 \X'tty: link https://pypi.org/project/passlib/'\fI\%passlib\fP\X'tty: link' .IP \(bu 2 \X'tty: link https://pypi.org/project/qrcode/'\fI\%QRCode\fP\X'tty: link' .IP \(bu 2 \X'tty: link https://pypi.org/project/webauthn/'\fI\%webauthn\fP\X'tty: link' .IP \(bu 2 \X'tty: link https://pypi.org/project/Authlib/'\fI\%authlib\fP\X'tty: link' .UNINDENT .sp Additionally, it assumes you\(aqll 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: .INDENT 0.0 .IP 1. 3 \X'tty: link https://pypi.python.org/pypi/flask-sqlalchemy/'\fI\%Flask\-SQLAlchemy\fP\X'tty: link' .IP 2. 3 \X'tty: link https://pypi.python.org/pypi/mongoengine/'\fI\%MongoEngine\fP\X'tty: link' .IP 3. 3 \X'tty: link https://docs.peewee-orm.com/en/latest/peewee/playhouse.html#flask-utils'\fI\%Peewee Flask utils\fP\X'tty: link' .IP 4. 3 \X'tty: link https://pypi.python.org/pypi/pony/'\fI\%PonyORM\fP\X'tty: link' \- NOTE: not currently working \- Help needed!. .IP 5. 3 \X'tty: link https://docs.sqlalchemy.org/en/20/orm/session_basics.html'\fI\%SQLAlchemy sessions\fP\X'tty: link' .UNINDENT .SH GETTING STARTED .SS Installation .sp Installing Flask\-Security\-Too using: .INDENT 0.0 .INDENT 3.5 .sp .EX pip install flask\-security\-too .EE .UNINDENT .UNINDENT .sp will install the basic package along with its required dependencies: .INDENT 0.0 .IP \(bu 2 Flask .IP \(bu 2 Flask\-Login .IP \(bu 2 Flask\-Principal .IP \(bu 2 Flask\-WTF .IP \(bu 2 email\-validator .IP \(bu 2 itsdangerous .IP \(bu 2 passlib .IP \(bu 2 Blinker .UNINDENT .sp 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 \(aqextras\(aq that can reduce the hassle of figuring out all the required packages. You can install these using the standard pip syntax: .INDENT 0.0 .INDENT 3.5 .sp .EX pip install flask\-security\-too[extra1,extra2, ...] .EE .UNINDENT .UNINDENT .sp Supported extras are: .INDENT 0.0 .IP \(bu 2 \fBbabel\fP \- Translation services. It will install babel and Flask\-Babel. .IP \(bu 2 \fBfsqla\fP \- Use flask\-sqlalchemy and sqlalchemy as your storage interface. .IP \(bu 2 \fBcommon\fP \- Install Flask\-Mailman, bcrypt (the default password hash), and bleach. .IP \(bu 2 \fBmfa\fP \- 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. .UNINDENT .sp Your application will also need a database backend: .INDENT 0.0 .IP \(bu 2 Sqlite is supported out of the box. .IP \(bu 2 For PostgreSQL install \X'tty: link https://pypi.org/project/psycopg2/'\fI\%psycopg2\fP\X'tty: link'\&. .IP \(bu 2 For MySQL install \X'tty: link https://pypi.org/project/PyMySQL/'\fI\%pymysql\fP\X'tty: link'\&. .IP \(bu 2 For MongoDB install \X'tty: link https://pypi.org/project/mongoengine/'\fI\%Mongoengine\fP\X'tty: link'\&. .UNINDENT .sp For additional details on configuring your database engine connector \- refer to \X'tty: link https://docs.sqlalchemy.org/en/14/core/engines.html'\fI\%sqlalchemy_engine\fP\X'tty: link' .SS Quick Start .sp There are some complete (but simple) examples available in the \fIexamples\fP directory of the \X'tty: link https://github.com/Flask-Middleware/flask-security'\fI\%Flask\-Security repo\fP\X'tty: link'\&. .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 The below quickstarts are just that \- they don\(aqt enable most of the features (such as registration, reset, etc.). They basically create a single user, and you can login as that user... that\(aqs 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. .UNINDENT .UNINDENT .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 The default \fBSECURITY_PASSWORD_HASH\fP is \(dqbcrypt\(dq \- so be sure to install bcrypt. If you opt for a different hash e.g. \(dqargon2\(dq you will need to install the appropriate package e.g. \X'tty: link https://pypi.org/project/argon2-cffi/'\fI\%argon_cffi\fP\X'tty: link'\&. .UNINDENT .UNINDENT .sp \fBDANGER:\fP .INDENT 0.0 .INDENT 3.5 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. .UNINDENT .UNINDENT .INDENT 0.0 .IP \(bu 2 \fI\%Basic SQLAlchemy Application\fP .IP \(bu 2 \fI\%Basic SQLAlchemy Application with session\fP .IP \(bu 2 \fI\%Basic MongoEngine Application\fP .IP \(bu 2 \fI\%Basic Peewee Application\fP .IP \(bu 2 \fI\%Mail Configuration\fP .IP \(bu 2 \fI\%Proxy Configuration\fP .IP \(bu 2 \fI\%Unit Testing Your Application\fP .UNINDENT .SS Basic SQLAlchemy Application .SS SQLAlchemy Install requirements .INDENT 0.0 .INDENT 3.5 .sp .EX $ python3 \-m venv pymyenv $ . pymyenv/bin/activate $ pip install flask\-security\-too[fsqla,common] .EE .UNINDENT .UNINDENT .SS SQLAlchemy Application .sp The following code sample illustrates how to get started as quickly as possible using Flask\-SQLAlchemy and the built\-in model mixins: .INDENT 0.0 .INDENT 3.5 .sp .EX 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[\(aqDEBUG\(aq] = True # Generate a nice key using secrets.token_urlsafe() app.config[\(aqSECRET_KEY\(aq] = os.environ.get(\(dqSECRET_KEY\(dq, \(aqpf9Wkove4IKEAXvy\-cQkeDPhv9Cb3Ag\-wyJILbq_dFw\(aq) # Bcrypt is set as default SECURITY_PASSWORD_HASH, which requires a salt # Generate a good salt using: secrets.SystemRandom().getrandbits(128) app.config[\(aqSECURITY_PASSWORD_SALT\(aq] = os.environ.get(\(dqSECURITY_PASSWORD_SALT\(dq, \(aq146585145368132386173505678016728509634\(aq) # have session and remember cookie be samesite (flask/flask_login) app.config[\(dqREMEMBER_COOKIE_SAMESITE\(dq] = \(dqstrict\(dq app.config[\(dqSESSION_COOKIE_SAMESITE\(dq] = \(dqstrict\(dq # Use an in\-memory db app.config[\(aqSQLALCHEMY_DATABASE_URI\(aq] = \(aqsqlite://\(aq # 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[\(dqSQLALCHEMY_ENGINE_OPTIONS\(dq] = { \(dqpool_pre_ping\(dq: True, } app.config[\(dqSQLALCHEMY_TRACK_MODIFICATIONS\(dq] = 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(\(dq/\(dq) @auth_required() def home(): return render_template_string(\(dqHello {{ current_user.email }}\(dq) # one time setup with app.app_context(): # Create User to test with db.create_all() if not app.security.datastore.find_user(email=\(dqtest@me.com\(dq): app.security.datastore.create_user(email=\(dqtest@me.com\(dq, password=hash_password(\(dqpassword\(dq)) db.session.commit() if __name__ == \(aq__main__\(aq: app.run() .EE .UNINDENT .UNINDENT .sp You can run this either with: .INDENT 0.0 .INDENT 3.5 .sp .EX flask run .EE .UNINDENT .UNINDENT .sp or: .INDENT 0.0 .INDENT 3.5 .sp .EX python app.py .EE .UNINDENT .UNINDENT .SS Basic SQLAlchemy Application with session .SS SQLAlchemy Install requirements .INDENT 0.0 .INDENT 3.5 .sp .EX $ python3 \-m venv pymyenv $ . pymyenv/bin/activate $ pip install flask\-security\-too[common] sqlalchemy .EE .UNINDENT .UNINDENT .SS SQLAlchemy Application (w/o Flask\-SQLAlchemy) .sp The following code sample illustrates how to get started as quickly as possible using \X'tty: link https://flask.palletsprojects.com/en/2.0.x/patterns/sqlalchemy/#declarative'\fI\%SQLAlchemy in a declarative way\fP\X'tty: link': .sp This example shows how to split your application into 3 files: app.py, database.py and models.py. .INDENT 0.0 .IP \(bu 2 app.py .INDENT 2.0 .INDENT 3.5 .sp .EX import os from flask import Flask, render_template_string from flask_security import Security, current_user, auth_required, hash_password, \e SQLAlchemySessionUserDatastore, permissions_accepted from database import db_session, init_db from models import User, Role # Create app app = Flask(__name__) app.config[\(aqDEBUG\(aq] = True # Generate a nice key using secrets.token_urlsafe() app.config[\(aqSECRET_KEY\(aq] = os.environ.get(\(dqSECRET_KEY\(dq, \(aqpf9Wkove4IKEAXvy\-cQkeDPhv9Cb3Ag\-wyJILbq_dFw\(aq) # Bcrypt is set as default SECURITY_PASSWORD_HASH, which requires a salt # Generate a good salt using: secrets.SystemRandom().getrandbits(128) app.config[\(aqSECURITY_PASSWORD_SALT\(aq] = os.environ.get(\(dqSECURITY_PASSWORD_SALT\(dq, \(aq146585145368132386173505678016728509634\(aq) # Don\(aqt worry if email has findable domain app.config[\(dqSECURITY_EMAIL_VALIDATOR_ARGS\(dq] = {\(dqcheck_deliverability\(dq: 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(\(dq/\(dq) @auth_required() def home(): return render_template_string(\(aqHello {{current_user.email}}!\(aq) @app.route(\(dq/user\(dq) @auth_required() @permissions_accepted(\(dquser\-read\(dq) def user_home(): return render_template_string(\(dqHello {{ current_user.email }} you are a user!\(dq) # 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=\(dquser\(dq, permissions={\(dquser\-read\(dq, \(dquser\-write\(dq} ) db_session.commit() if not app.security.datastore.find_user(email=\(dqtest@me.com\(dq): app.security.datastore.create_user(email=\(dqtest@me.com\(dq, password=hash_password(\(dqpassword\(dq), roles=[\(dquser\(dq]) db_session.commit() if __name__ == \(aq__main__\(aq: # run application (can also use flask run) app.run() .EE .UNINDENT .UNINDENT .IP \(bu 2 database.py .INDENT 2.0 .INDENT 3.5 .sp .EX from sqlalchemy import create_engine from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.ext.declarative import declarative_base engine = create_engine(\(aqsqlite:////tmp/test.db\(aq) 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) .EE .UNINDENT .UNINDENT .IP \(bu 2 models.py .INDENT 2.0 .INDENT 3.5 .sp .EX 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, \e String, ForeignKey class RolesUsers(Base): __tablename__ = \(aqroles_users\(aq id = Column(Integer(), primary_key=True) user_id = Column(\(aquser_id\(aq, Integer(), ForeignKey(\(aquser.id\(aq)) role_id = Column(\(aqrole_id\(aq, Integer(), ForeignKey(\(aqrole.id\(aq)) class Role(Base, RoleMixin): __tablename__ = \(aqrole\(aq 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__ = \(aquser\(aq 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(\(aqRole\(aq, secondary=\(aqroles_users\(aq, backref=backref(\(aqusers\(aq, lazy=\(aqdynamic\(aq)) .EE .UNINDENT .UNINDENT .UNINDENT .sp You can run this either with: .INDENT 0.0 .INDENT 3.5 .sp .EX flask run .EE .UNINDENT .UNINDENT .sp or: .INDENT 0.0 .INDENT 3.5 .sp .EX python app.py .EE .UNINDENT .UNINDENT .SS Basic MongoEngine Application .SS MongoEngine Install requirements .INDENT 0.0 .INDENT 3.5 .sp .EX $ python3 \-m venv pymyenv $ . pymyenv/bin/activate $ pip install flask\-security\-too[common] mongoengine .EE .UNINDENT .UNINDENT .SS MongoEngine Application .sp 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): .INDENT 0.0 .INDENT 3.5 .sp .EX 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, \e UserMixin, RoleMixin, auth_required, hash_password, permissions_accepted # Create app app = Flask(__name__) app.config[\(aqDEBUG\(aq] = True # Generate a nice key using secrets.token_urlsafe() app.config[\(aqSECRET_KEY\(aq] = os.environ.get(\(dqSECRET_KEY\(dq, \(aqpf9Wkove4IKEAXvy\-cQkeDPhv9Cb3Ag\-wyJILbq_dFw\(aq) # Bcrypt is set as default SECURITY_PASSWORD_HASH, which requires a salt # Generate a good salt using: secrets.SystemRandom().getrandbits(128) app.config[\(aqSECURITY_PASSWORD_SALT\(aq] = os.environ.get(\(dqSECURITY_PASSWORD_SALT\(dq, \(aq146585145368132386173505678016728509634\(aq) # Don\(aqt worry if email has findable domain app.config[\(dqSECURITY_EMAIL_VALIDATOR_ARGS\(dq] = {\(dqcheck_deliverability\(dq: False} # Create database connection object db_name = \(dqmydatabase\(dq db = connect(alias=db_name, db=db_name, host=\(dqmongodb://localhost\(dq, port=27017) class Role(Document, RoleMixin): name = StringField(max_length=80, unique=True) description = StringField(max_length=255) permissions = ListField(required=False) meta = {\(dqdb_alias\(dq: 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 = {\(dqdb_alias\(dq: db_name} # Setup Flask\-Security user_datastore = MongoEngineUserDatastore(db, User, Role) app.security = Security(app, user_datastore) # Views @app.route(\(dq/\(dq) @auth_required() def home(): return render_template_string(\(dqHello {{ current_user.email }}\(dq) @app.route(\(dq/user\(dq) @auth_required() @permissions_accepted(\(dquser\-read\(dq) def user_home(): return render_template_string(\(dqHello {{ current_user.email }} you are a user!\(dq) # one time setup with app.app_context(): # Create a user and role to test with app.security.datastore.find_or_create_role( name=\(dquser\(dq, permissions={\(dquser\-read\(dq, \(dquser\-write\(dq} ) if not app.security.datastore.find_user(email=\(dqtest@me.com\(dq): app.security.datastore.create_user(email=\(dqtest@me.com\(dq, password=hash_password(\(dqpassword\(dq), roles=[\(dquser\(dq]) if __name__ == \(aq__main__\(aq: # run application (can also use flask run) app.run() .EE .UNINDENT .UNINDENT .SS Basic Peewee Application .SS Peewee Install requirements .INDENT 0.0 .INDENT 3.5 .sp .EX $ python3 \-m venv pymyenv $ . pymyenv/bin/activate $ pip install flask\-security\-too[common] peewee .EE .UNINDENT .UNINDENT .SS Peewee Application .sp The following code sample illustrates how to get started as quickly as possible using Peewee: .INDENT 0.0 .INDENT 3.5 .sp .EX import os from flask import Flask, render_template_string from playhouse.flask_utils import FlaskDB from peewee import * from flask_security import Security, PeeweeUserDatastore, \e UserMixin, RoleMixin, auth_required, hash_password # Create app app = Flask(__name__) app.config[\(aqDEBUG\(aq] = True # Generate a nice key using secrets.token_urlsafe() app.config[\(aqSECRET_KEY\(aq] = os.environ.get(\(dqSECRET_KEY\(dq, \(aqpf9Wkove4IKEAXvy\-cQkeDPhv9Cb3Ag\-wyJILbq_dFw\(aq) # Bcrypt is set as default SECURITY_PASSWORD_HASH, which requires a salt # Generate a good salt using: secrets.SystemRandom().getrandbits(128) app.config[\(aqSECURITY_PASSWORD_SALT\(aq] = os.environ.get(\(dqSECURITY_PASSWORD_SALT\(dq, \(aq146585145368132386173505678016728509634\(aq) app.config[\(aqDATABASE\(aq] = { \(aqname\(aq: \(aqexample.db\(aq, \(aqengine\(aq: \(aqpeewee.SqliteDatabase\(aq, } # 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=\(aqroles\(aq) role = ForeignKeyField(Role, related_name=\(aqusers\(aq) 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(\(aq/\(aq) @auth_required() def home(): return render_template_string(\(dqHello {{ current_user.email }}\(dq) # 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=\(dqtest@me.com\(dq): app.security.datastore.create_user(email=\(dqtest@me.com\(dq, password=hash_password(\(dqpassword\(dq)) if __name__ == \(aq__main__\(aq: app.run() .EE .UNINDENT .UNINDENT .SS Mail Configuration .sp Flask\-Security integrates with an outgoing mail service via the \fBmail_util_cls\fP which is part of initial configuration. The default class \fI\%flask_security.MailUtil\fP utilizes the \X'tty: link https://pypi.org/project/flask-mailman/'\fI\%Flask\-Mailman\fP\X'tty: link' package. Be sure to add flask_mailman to your requirements.txt. The older and no longer maintained package \X'tty: link https://pypi.org/project/Flask-Mail/'\fI\%Flask\-Mail\fP\X'tty: link' is also (still) supported. .sp The following code illustrates a basic setup, which could be added to the basic application code in the previous section: .INDENT 0.0 .INDENT 3.5 .sp .EX # At top of file from flask_mailman import Mail # After \(aqCreate app\(aq app.config[\(aqMAIL_SERVER\(aq] = \(aqsmtp.example.com\(aq app.config[\(aqMAIL_PORT\(aq] = 587 app.config[\(aqMAIL_USE_TLS\(aq] = True app.config[\(aqMAIL_USERNAME\(aq] = \(aqusername\(aq app.config[\(aqMAIL_PASSWORD\(aq] = \(aqpassword\(aq mail = Mail(app) .EE .UNINDENT .UNINDENT .sp To learn more about the various Flask\-Mailman settings to configure it to work with your particular email server configuration, please see the \X'tty: link https://waynerv.github.io/flask-mailman/'\fI\%Flask\-Mailman documentation\fP\X'tty: link'\&. .SS Proxy Configuration .sp 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: .INDENT 0.0 .INDENT 3.5 .sp .EX # At top of file from werkzeug.middleware.proxy_fix import ProxyFix # After \(aqCreate app\(aq app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1) .EE .UNINDENT .UNINDENT .sp To learn more about the \fBProxyFix\fP middleware, please see the \X'tty: link https://werkzeug.palletsprojects.com/en/2.0.x/middleware/proxy_fix/#module-werkzeug.middleware.proxy_fix'\fI\%Werkzeug documentation\fP\X'tty: link'\&. .SS Unit Testing Your Application .sp 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: .INDENT 0.0 .IP \(bu 2 \(aqPure\(aq unit test \- mocking out all lower level objects (such as the data store) .IP \(bu 2 Complete app with in\-memory/temporary DB (with little or no mocking). .UNINDENT .sp Look in the \X'tty: link https://github.com/Flask-Middleware/flask-security'\fI\%Flask\-Security repo\fP\X'tty: link' \fIexamples\fP directory for actual code that implements the second approach which is much simpler and with an in\-memory DB fairly fast. .sp You also might want to set the following configurations in your conftest.py: .INDENT 0.0 .INDENT 3.5 .sp .EX app.config[\(dqWTF_CSRF_ENABLED\(dq] = False # Our test emails/domain isn\(aqt necessarily valid app.config[\(dqSECURITY_EMAIL_VALIDATOR_ARGS\(dq] = {\(dqcheck_deliverability\(dq: False} # Make this plaintext for most tests \- reduces unit test time by 50% app.config[\(dqSECURITY_PASSWORD_HASH\(dq] = \(dqplaintext\(dq .EE .UNINDENT .UNINDENT .SS Features .sp Flask\-Security allows you to quickly add common security mechanisms to your Flask application. They include: .SS Session Based Authentication .sp Session based authentication is fulfilled entirely by the \X'tty: link https://flask-login.readthedocs.org/en/latest/'\fI\%Flask\-Login\fP\X'tty: link' extension. Flask\-Security handles the configuration of Flask\-Login automatically based on a few of its own configuration values and uses Flask\-Login\(aqs \X'tty: link https://flask-login.readthedocs.io/en/latest/#alternative-tokens'\fI\%alternative token\fP\X'tty: link' feature to associate the value of \fBfs_uniquifier\fP with the user. (This enables easily invalidating all existing sessions for a given user without having to change their user id). \X'tty: link https://flask-wtf.readthedocs.io/en/1.0.x/csrf/'\fI\%Flask\-WTF\fP\X'tty: link' 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. .SS Role/Identity Based Access .sp 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 \fIAdmin\fP, \fIEditor\fP, \fISuperUser\fP, 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 \X'tty: link https://pypi.org/project/Flask-Principal/'\fI\%Flask\-Principal\fP\X'tty: link' 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\(aqd like to implement even more granular access control (such as per\-object), you can refer to the Flask\-Principal \X'tty: link http://packages.python.org/Flask-Principal/#granular-resource-protection'\fI\%documentation on this topic\fP\X'tty: link'\&. .SS Password Hashing .sp Password hashing is enabled with \X'tty: link https://passlib.readthedocs.io/en/stable/'\fI\%passlib\fP\X'tty: link'\&. Passwords are hashed with the \X'tty: link https://en.wikipedia.org/wiki/Bcrypt'\fI\%bcrypt\fP\X'tty: link' function by default but you can easily configure the hashing algorithm. You should \fBalways use a hashing algorithm\fP in your production environment. Hash algorithms not listed in \fBSECURITY_PASSWORD_SINGLE_HASH\fP 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 \fBSECURITY_PASSWORD_SALT\fP\&. A good way to generate this is: .INDENT 0.0 .INDENT 3.5 .sp .EX secrets.SystemRandom().getrandbits(128) .EE .UNINDENT .UNINDENT .sp Bear in mind passlib does not assume which algorithm you will choose and may require additional libraries to be installed. .SS Password Validation and Complexity .sp Consult \fI\%Password Validation and Complexity\fP\&. .SS Basic HTTP Authentication .sp 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. .SS Token Authentication .sp Token based authentication can be used by retrieving the user auth token from an authentication endpoint (e.g. \fB/login\fP, \fB/us\-signin\fP, \fB/wan\-signin\fP). Perform an HTTP POST with a query param of \fBinclude_auth_token\fP 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 \fIAuthentication\-Token\fP and the default query string parameter name is \fIauth_token\fP\&. .sp Authentication tokens are generated using a uniquifier field in the user\(aqs UserModel. By default that field is \fBfs_uniquifier\fP\&. This means that if that field is changed (via \fI\%UserDatastore.set_uniquifier()\fP) 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: \fBfs_token_uniquifier\fP and that will be used instead, thus isolating password changes from authentication tokens. That attribute can be changed via \fI\%UserDatastore.set_token_uniquifier()\fP\&. This attribute should have \fBunique=True\fP\&. Unlike \fBfs_uniquifier\fP, it can be set to \fBnullable\fP \- it will automatically be generated at first use if null. .sp Authentication tokens have 2 options for specifying expiry time \fI\%SECURITY_TOKEN_MAX_AGE\fP is applied to ALL authentication tokens. Each authentication token can itself have an embedded expiry value (settable via the \fI\%SECURITY_TOKEN_EXPIRE_TIMESTAMP\fP callable). .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 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 \fI\%Freshness\fP and \fI\%CSRF\fP .UNINDENT .UNINDENT .SS Two\-factor Authentication .sp Two\-factor authentication is enabled by generating time\-based one time passwords (Tokens). The tokens are generated using the users \X'tty: link https://passlib.readthedocs.io/en/stable/narr/totp-tutorial.html#overview'\fI\%totp secret\fP\X'tty: link', 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 \X'tty: link https://pypi.org/project/qrcode/'\fI\%qrcode\fP\X'tty: link' library. Please read \fI\%Theory of Operation\fP for more details. .sp The Two\-factor feature offers the ability for a user to \(aqrescue\(aq 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 \fI\%SECURITY_MULTI_FACTOR_RECOVERY_CODES\fP). .SS Unified Sign In .sp \fBThis feature is in Beta \- mostly due to it being brand new and little to no production soak time\fP .sp Unified sign in provides a generalized login endpoint that takes an \fIidentity\fP and a \fIpasscode\fP; where (based on configuration): .INDENT 0.0 .INDENT 3.5 .INDENT 0.0 .IP \(bu 2 \fIidentity\fP is any of \fI\%SECURITY_USER_IDENTITY_ATTRIBUTES\fP (e.g. email, username, phone) .IP \(bu 2 \fIpasscode\fP is a password or a one\-time code (delivered via email, SMS, or authenticator app) .UNINDENT .UNINDENT .UNINDENT .sp Please see this \X'tty: link https://en.wikipedia.org/wiki/Multi-factor_authentication'\fI\%Wikipedia\fP\X'tty: link' article about multi\-factor authentication. .sp 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. .sp 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. .sp Using SMS or an authenticator app means you are providing \(dqsomething you have\(dq (the mobile device) and either \(dqsomething you know\(dq (passcode to unlock your device) or \(dqsomething you are\(dq (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 \X'tty: link https://docs.microsoft.com/en-us/azure/active-directory/user-help/user-help-auth-app-overview'\fI\%Microsoft\(aqs\fP\X'tty: link' version. .sp Note that by configuring \fI\%SECURITY_US_ENABLED_METHODS\fP an application can use this endpoint JUST with identity/password or in fact disallow passwords altogether. .sp 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 \fI\%SECURITY_US_MFA_REQUIRED\fP configuration determines which primary authentication mechanisms require a second factor. By default limited to \fBemail\fP and \fBpassword\fP (if two\-factor is enabled). .sp Be aware that by default, the \fI\%SECURITY_US_SETUP_URL\fP endpoint is protected with a freshness check (see \fI\%flask_security.auth_required()\fP) 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\(aqt required. .sp \fICurrent Limited Functionality\fP: .INDENT 0.0 .INDENT 3.5 .INDENT 0.0 .IP \(bu 2 Change password does not work if a user registers without a password. However forgot\-password will allow the user to set a new password. .IP \(bu 2 Registration and Confirmation only work with email \- so while you can enable multiple authentication methods, you still have to register with email. .UNINDENT .UNINDENT .UNINDENT .SS WebAuthn .sp \fBThis feature is in Beta \- mostly due to it being brand new and little to no production soak time\fP .sp WebAuthn is a standardized protocol that connects authenticators (such as YubiKey and mobile biometrics) with websites. Flask\-Security supports using WebAuthn keys as either \(aqfirst\(aq or \(aqsecondary\(aq authenticators. Please read \fI\%WebAuthn\fP for more details. .SS Email Confirmation .sp 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\(aqs account will be set to \(aqconfirmed\(aq. 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). .SS Password Reset/Recovery .sp 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. .sp As with password change \- this will update the the user\(aqs \fBfs_uniquifier\fP attribute which will invalidate all existing sessions AND (by default) all authentication tokens. .SS User Registration .sp 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 \X'tty: link https://pypi.org/project/email-validator/'\fI\%email_validator\fP\X'tty: link' package. .sp The \fI\%SECURITY_USERNAME_ENABLE\fP configuration option, when set to \fBTrue\fP, 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 \fI\%SECURITY_USER_IDENTITY_ATTRIBUTES\fP\&. .SS Password Change .sp 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 \fBfs_uniquifier\fP\&. 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. .sp \fINOTE\fP: The \fBfs_uniquifier\fP 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 \fBfs_token_uniquifier\fP, then that will be used when generating authentication tokens and so won\(aqt be affected by password changes. .SS Login Tracking .sp Flask\-Security can, if configured, keep track of basic login events and statistics. They include: .INDENT 0.0 .IP \(bu 2 Last login date .IP \(bu 2 Current login date .IP \(bu 2 Last login IP address .IP \(bu 2 Current login IP address .IP \(bu 2 Total login count .UNINDENT .SS JSON/Ajax Support .sp Flask\-Security supports JSON/Ajax requests where appropriate. Please look at \fI\%CSRF\fP for details on how to work with JSON and Single Page Applications. More specifically JSON is supported for the following operations: .INDENT 0.0 .IP \(bu 2 Login requests .IP \(bu 2 Unified sign in requests .IP \(bu 2 Registration requests .IP \(bu 2 Change password requests .IP \(bu 2 Confirmation requests .IP \(bu 2 Forgot password requests .IP \(bu 2 Passwordless login requests .IP \(bu 2 Two\-factor login requests .IP \(bu 2 Change two\-factor method requests .IP \(bu 2 WebAuthn registration and signin requests .IP \(bu 2 Two\-Factor recovery code requests .UNINDENT .sp In addition, Single\-Page\-Applications (like those built with Vue, Angular, and React) are supported via customizable redirect links. .sp Note: All registration requests done through JSON/Ajax utilize the \fBconfirm_register_form\fP\&. .SS Command Line Interface .sp Basic \X'tty: link https://palletsprojects.com/p/click/'\fI\%Click\fP\X'tty: link' commands for managing users and roles are automatically registered. They can be completely disabled or their names can be changed. Run \fBflask \-\-help\fP and look for users and roles. .SS Social/Oauth Authentication .sp Flask\-Security provides a thin layer which integrates \X'tty: link https://authlib.org/'\fI\%authlib\fP\X'tty: link' 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 \X'tty: link https://github.com/authlib/loginpass'\fI\%loginpass\fP\X'tty: link' for many examples). .sp See \fI\%flask_security.OAuthGlue\fP and \fI\%flask_security.FsOAuthProvider\fP .sp 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. .sp See \X'tty: link https://docs.authlib.org/en/latest/client/flask.html'\fI\%Flask OAuth Client\fP\X'tty: link' 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. .sp We have seen issues with some providers when \fISESSION_COOKIE_SAMESITE\fP = \(dqstrict\(dq. 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. .sp A very simple example of configuring social auth with Flask\-Security is available in the \fIexamples\fP directory. .SS Configuration .sp \fBWARNING:\fP .INDENT 0.0 .INDENT 3.5 Be sure to set all configuration (in app.config[\(dqxxx\(dq]) \fIPRIOR\fP to instantiating the Security class or calling security.init_app(). .UNINDENT .UNINDENT .sp The following configuration values are used by Flask\-Security: .SS Core .sp These configuration keys are used globally across all features. .INDENT 0.0 .TP .B 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: \fBsecrets.token_urlsafe()\fP .UNINDENT .INDENT 0.0 .TP .B SECURITY_BLUEPRINT_NAME Specifies the name for the Flask\-Security blueprint. .sp Default: \fB\(dqsecurity\(dq\fP\&. .UNINDENT .INDENT 0.0 .TP .B SECURITY_URL_PREFIX Specifies the URL prefix for the Flask\-Security blueprint. .sp Default: \fBNone\fP\&. .UNINDENT .INDENT 0.0 .TP .B SECURITY_STATIC_FOLDER Specifies the folder name for static files (webauthn). .sp Default: \fB\(dqstatic\(dq\fP\&. .sp Added in version 5.1.0. .UNINDENT .INDENT 0.0 .TP .B SECURITY_STATIC_FOLDER_URL Specifies the URL for static files used by Flask\-Security (webauthn). See Flask documentation \X'tty: link https://flask.palletsprojects.com/en/latest/blueprints/#static-files'\fI\%https://flask.palletsprojects.com/en/latest/blueprints/#static\-files\fP\X'tty: link' .sp Default: \fB\(dq/fs\-static\(dq\fP\&. .sp Added in version 5.1.0. .UNINDENT .INDENT 0.0 .TP .B SECURITY_SUBDOMAIN Specifies the subdomain for the Flask\-Security blueprint. If your authenticated content is on a different subdomain, also enable \fI\%SECURITY_REDIRECT_ALLOW_SUBDOMAINS\fP\&. .sp Default: \fBNone\fP\&. .UNINDENT .INDENT 0.0 .TP .B SECURITY_FLASH_MESSAGES Specifies whether or not to flash messages during security procedures. .sp Default: \fBTrue\fP\&. .UNINDENT .INDENT 0.0 .TP .B SECURITY_I18N_DOMAIN Specifies the name for domain used for translations. .sp Default: \fB\(dqflask_security\(dq\fP\&. .UNINDENT .INDENT 0.0 .TP .B SECURITY_I18N_DIRNAME Specifies the directory containing the \fBMO\fP 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 \fBbuiltin\fP uses translations shipped with Flask\-Security. .sp Default: \fB\(dqbuiltin\(dq\fP\&. .sp Changed in version 5.2.0: \(dqbuiltin\(dq is a special name which will be interpreted as the \fBtranslations\fP directory within the installation of Flask\-Security. .UNINDENT .INDENT 0.0 .TP .B SECURITY_PASSWORD_HASH Specifies the password hash algorithm to use when hashing passwords. Recommended values for production systems are \fBbcrypt\fP, \fBargon2\fP, \fBsha512_crypt\fP, or \fBpbkdf2_sha512\fP\&. Some algorithms require the installation of a backend package (e.g. \X'tty: link https://pypi.org/project/bcrypt/'\fI\%bcrypt\fP\X'tty: link', \X'tty: link https://pypi.org/project/argon2-cffi/'\fI\%argon2\fP\X'tty: link'). .sp Default: \fB\(dqbcrypt\(dq\fP\&. .UNINDENT .INDENT 0.0 .TP .B SECURITY_PASSWORD_SCHEMES List of support password hash algorithms. \fBSECURITY_PASSWORD_HASH\fP must be from this list. Passwords encrypted with any of these schemes will be honored. .UNINDENT .INDENT 0.0 .TP .B 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 \fBSECURITY_PASSWORD_HASH\fP\&. .sp Default: \fB[\(dqauto\(dq]\fP which means any password found that wasn\(aqt hashed using \fBSECURITY_PASSWORD_HASH\fP will be re\-hashed. .UNINDENT .INDENT 0.0 .TP .B 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: \fBsecrets.SystemRandom().getrandbits(128)\fP\&. .sp Default: \fBNone\fP\&. .UNINDENT .INDENT 0.0 .TP .B SECURITY_PASSWORD_SINGLE_HASH A list of schemes that should not be hashed twice. By default, passwords are hashed twice, first with \fBSECURITY_PASSWORD_SALT\fP, and then with a random salt. .sp Default: a list of known schemes not working with double hashing (\fIdjango_{digest}\fP, \fIplaintext\fP). .UNINDENT .INDENT 0.0 .TP .B SECURITY_HASHING_SCHEMES List of algorithms used for encrypting/hashing sensitive data within a token (Such as is sent with confirmation or reset password). .sp Default: \fB[\(dqsha256_crypt\(dq, \(dqhex_md5\(dq]\fP\&. .UNINDENT .INDENT 0.0 .TP .B SECURITY_DEPRECATED_HASHING_SCHEMES List of deprecated algorithms used for creating and validating tokens. .sp Default: \fB[\(dqhex_md5\(dq]\fP\&. .UNINDENT .INDENT 0.0 .TP .B SECURITY_PASSWORD_HASH_OPTIONS Specifies additional options to be passed to the hashing method. This is deprecated as of passlib 1.7. .sp Deprecated since version 3.4.0: see: \fI\%SECURITY_PASSWORD_HASH_PASSLIB_OPTIONS\fP .UNINDENT .INDENT 0.0 .TP .B SECURITY_PASSWORD_HASH_PASSLIB_OPTIONS Pass additional options to the various hashing methods. This is a dict of the form \fB{__