Simple security for Flask apps
Flask-Security provides optional security features that can be enabled through configuration. These features include email confirmation, password recovery, user registration, password changing, and passwordless authentication. Each feature can be independently enabled and customized.
Functions for managing email confirmation workflows when SECURITY_CONFIRMABLE is enabled.
def send_confirmation_instructions(user):
"""
Send confirmation instructions to user via email.
Args:
user: User instance requiring confirmation
Returns:
None
"""
def generate_confirmation_link(user):
"""
Generate confirmation URL and token for user.
Args:
user: User instance
Returns:
str: Confirmation URL
"""
def generate_confirmation_token(user):
"""
Generate confirmation token for user.
Args:
user: User instance
Returns:
str: Confirmation token
"""
def requires_confirmation(user):
"""
Check if user requires email confirmation.
Args:
user: User instance
Returns:
bool: True if user needs confirmation
"""
def confirm_email_token_status(token):
"""
Validate confirmation token status.
Args:
token (str): Confirmation token
Returns:
tuple: (expired, invalid, user) status
"""
def confirm_user(user):
"""
Mark user as confirmed and update timestamp.
Args:
user: User instance to confirm
Returns:
bool: True if confirmation successful
"""Functions for password reset workflows when SECURITY_RECOVERABLE is enabled.
def send_reset_password_instructions(user):
"""
Send password reset instructions to user via email.
Args:
user: User instance requesting password reset
Returns:
None
"""
def send_password_reset_notice(user):
"""
Send password reset notification to user.
Args:
user: User whose password was reset
Returns:
None
"""
def generate_reset_password_token(user):
"""
Generate password reset token for user.
Args:
user: User instance
Returns:
str: Password reset token
"""
def reset_password_token_status(token):
"""
Validate password reset token status.
Args:
token (str): Reset token
Returns:
tuple: (expired, invalid, user) status
"""
def update_password(user, password):
"""
Update user password and handle related operations.
Args:
user: User instance
password (str): New plain text password
Returns:
None
"""Functions for user registration when SECURITY_REGISTERABLE is enabled.
def register_user(**kwargs):
"""
Register a new user with the provided data.
Args:
**kwargs: User data (email, password, etc.)
Returns:
User: Newly created user instance
"""Functions for password change workflows when SECURITY_CHANGEABLE is enabled.
def send_password_changed_notice(user):
"""
Send password change notification to user.
Args:
user: User whose password was changed
Returns:
None
"""
def change_user_password(user, password):
"""
Change user password and handle notifications.
Args:
user: User instance
password (str): New plain text password
Returns:
None
"""Functions for passwordless authentication when SECURITY_PASSWORDLESS is enabled.
def send_login_instructions(user):
"""
Send login instructions with token to user via email.
Args:
user: User instance requesting passwordless login
Returns:
None
"""
def generate_login_token(user):
"""
Generate login token for passwordless authentication.
Args:
user: User instance
Returns:
str: Login token
"""
def login_token_status(token):
"""
Validate login token status.
Args:
token (str): Login token
Returns:
tuple: (expired, invalid, user) status
"""# Enable/disable features in Flask configuration
app.config['SECURITY_CONFIRMABLE'] = True # Email confirmation
app.config['SECURITY_REGISTERABLE'] = True # User registration
app.config['SECURITY_RECOVERABLE'] = True # Password recovery
app.config['SECURITY_CHANGEABLE'] = True # Password changing
app.config['SECURITY_PASSWORDLESS'] = True # Passwordless login
app.config['SECURITY_TRACKABLE'] = True # User activity tracking# Email settings for feature notifications
app.config['SECURITY_EMAIL_SENDER'] = 'noreply@example.com'
app.config['SECURITY_SEND_REGISTER_EMAIL'] = True
app.config['SECURITY_SEND_PASSWORD_CHANGE_EMAIL'] = True
app.config['SECURITY_SEND_PASSWORD_RESET_EMAIL'] = True
app.config['SECURITY_SEND_PASSWORD_RESET_NOTICE_EMAIL'] = True
# Time limits for tokens
app.config['SECURITY_CONFIRM_EMAIL_WITHIN'] = '5 days'
app.config['SECURITY_RESET_PASSWORD_WITHIN'] = '5 days'
app.config['SECURITY_LOGIN_WITHIN'] = '1 days'from flask_security import register_user, send_confirmation_instructions
# Enable confirmable feature
app.config['SECURITY_CONFIRMABLE'] = True
app.config['SECURITY_LOGIN_WITHOUT_CONFIRMATION'] = False
@app.route('/register', methods=['POST'])
def register():
email = request.form.get('email')
password = request.form.get('password')
# Register user (automatically unconfirmed)
user = register_user(email=email, password=password)
# Send confirmation email
send_confirmation_instructions(user)
return "Please check your email to confirm your account"
@app.route('/confirm/<token>')
def confirm_email(token):
expired, invalid, user = confirm_email_token_status(token)
if expired:
return "Confirmation link has expired"
if invalid:
return "Invalid confirmation link"
# Confirm the user
if confirm_user(user):
login_user(user)
return "Email confirmed! You are now logged in."
return "Confirmation failed"
# Check if user needs confirmation
@app.before_request
def check_confirmation():
if (current_user.is_authenticated and
requires_confirmation(current_user)):
return redirect(url_for('send_confirmation'))from flask_security import send_reset_password_instructions, update_password
# Enable recoverable feature
app.config['SECURITY_RECOVERABLE'] = True
@app.route('/forgot-password', methods=['POST'])
def forgot_password():
email = request.form.get('email')
user = user_datastore.find_user(email=email)
if user:
send_reset_password_instructions(user)
# Always show success to prevent email enumeration
return "Password reset instructions sent"
@app.route('/reset-password/<token>', methods=['GET', 'POST'])
def reset_password(token):
expired, invalid, user = reset_password_token_status(token)
if expired or invalid:
return "Invalid or expired reset link"
if request.method == 'POST':
new_password = request.form.get('password')
update_password(user, new_password)
# Send notification
send_password_reset_notice(user)
return "Password updated successfully"
return render_template('reset_password.html', token=token)from flask_security import register_user
# Enable registration
app.config['SECURITY_REGISTERABLE'] = True
@app.route('/api/register', methods=['POST'])
def api_register():
data = request.get_json()
try:
user = register_user(
email=data['email'],
password=data['password'],
first_name=data.get('first_name'),
last_name=data.get('last_name')
)
if app.config.get('SECURITY_CONFIRMABLE'):
return {
'message': 'Registration successful. Please confirm your email.',
'requires_confirmation': True
}
else:
login_user(user)
return {
'message': 'Registration successful',
'user': user.get_security_payload(),
'auth_token': user.get_auth_token()
}
except Exception as e:
return {'error': str(e)}, 400from flask_security import change_user_password, verify_password
# Enable changeable feature
app.config['SECURITY_CHANGEABLE'] = True
@app.route('/change-password', methods=['POST'])
@login_required
def change_password():
current_password = request.form.get('current_password')
new_password = request.form.get('new_password')
# Verify current password
if not verify_password(current_password, current_user.password):
return "Current password is incorrect", 400
# Change password
change_user_password(current_user, new_password)
return "Password changed successfully"
# Force password change for new users
@app.before_request
def check_password_change():
if (current_user.is_authenticated and
getattr(current_user, 'force_password_change', False)):
if request.endpoint != 'change_password':
return redirect(url_for('change_password'))from flask_security import send_login_instructions, login_token_status
# Enable passwordless feature
app.config['SECURITY_PASSWORDLESS'] = True
@app.route('/passwordless-login', methods=['POST'])
def passwordless_login():
email = request.form.get('email')
user = user_datastore.find_user(email=email)
if user and user.is_active:
send_login_instructions(user)
return "Login instructions sent to your email"
@app.route('/login/<token>')
def token_login(token):
expired, invalid, user = login_token_status(token)
if expired:
return "Login link has expired"
if invalid:
return "Invalid login link"
# Log in the user
login_user(user)
return redirect(url_for('dashboard'))# Enable tracking feature
app.config['SECURITY_TRACKABLE'] = True
@app.route('/user-stats')
@login_required
def user_stats():
return {
'last_login_at': current_user.last_login_at,
'current_login_at': current_user.current_login_at,
'last_login_ip': current_user.last_login_ip,
'current_login_ip': current_user.current_login_ip,
'login_count': current_user.login_count
}
# Add tracking fields to User model
class User(db.Model, UserMixin):
# ... other fields ...
# Tracking fields (automatically managed by Flask-Security)
last_login_at = db.Column(db.DateTime())
current_login_at = db.Column(db.DateTime())
last_login_ip = db.Column(db.String(100))
current_login_ip = db.Column(db.String(100))
login_count = db.Column(db.Integer)from flask_security.signals import user_registered, user_confirmed
# Custom welcome workflow
@user_registered.connect_via(app)
def send_welcome_series(sender, **extra):
user = extra['user']
if app.config.get('SECURITY_CONFIRMABLE'):
# Schedule welcome series after confirmation
schedule_task('send_welcome_series', user.id, delay='1 hour')
else:
# Send immediately
send_welcome_email(user)
@user_confirmed.connect_via(app)
def handle_confirmation(sender, **extra):
user = extra['user']
# Grant default role
default_role = user_datastore.find_role('member')
user_datastore.add_role_to_user(user, default_role)
# Send welcome email
send_welcome_email(user)
# Start onboarding process
start_onboarding_flow(user)
user_datastore.commit()tessl i tessl/pypi-flask-security@3.0.0