Quickly add security features to your Flask application.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Password hashing, validation, reset workflows, security utilities, and password policy enforcement for managing user credentials securely in Flask applications.
Core functions for secure password storage and verification using modern hashing algorithms.
def hash_password(password):
"""
Hash a password using the configured hashing algorithm.
Parameters:
- password: Plain text password to hash
Returns:
Hashed password string suitable for database storage
"""
def verify_password(password, password_hash):
"""
Verify a password against its stored hash.
Parameters:
- password: Plain text password to verify
- password_hash: Stored password hash
Returns:
True if password matches hash, False otherwise
"""
def verify_and_update_password(password, user):
"""
Verify password and update hash if algorithm upgrade is needed.
Parameters:
- password: Plain text password to verify
- user: User object with password hash
Returns:
True if password matches, False otherwise
"""Forms for users to change their existing passwords.
class ChangePasswordForm(Form):
"""
Form for users to change their current password.
Fields:
- password: Current password field for verification
- new_password: New password field
- new_password_confirm: New password confirmation field
- submit: Submit button
"""
password: PasswordField
new_password: PasswordField
new_password_confirm: PasswordField
submit: SubmitFieldForms for password recovery workflow when users forget their passwords.
class ForgotPasswordForm(Form):
"""
Form for requesting password reset instructions.
Fields:
- user: Email or username field for identity lookup
- submit: Submit button
"""
user: StringField
submit: SubmitField
class ResetPasswordForm(Form):
"""
Form for setting new password during reset process.
Fields:
- password: New password field
- password_confirm: New password confirmation field
- submit: Submit button
"""
password: PasswordField
password_confirm: PasswordField
submit: SubmitFieldFunctions for managing the password reset workflow and token validation.
def send_reset_password_instructions(user):
"""
Send password reset instructions via email.
Parameters:
- user: User object to send reset instructions to
Returns:
True if email sent successfully, False otherwise
"""
def send_password_reset_notice(user):
"""
Send notification that password was reset (security notice).
Parameters:
- user: User object whose password was reset
Returns:
True if email sent successfully, False otherwise
"""
def generate_reset_password_token(user):
"""
Generate password reset token for user.
Parameters:
- user: User object to generate token for
Returns:
Reset token string
"""
def reset_password_token_status(token):
"""
Validate password reset token and return status information.
Parameters:
- token: Reset token to validate
Returns:
Tuple of (expired: bool, invalid: bool, user: User|None)
"""
def update_password(user, password):
"""
Update user's password with proper hashing and validation.
Parameters:
- user: User object to update password for
- password: New plain text password
Returns:
True if password updated successfully, False otherwise
"""Functions for administrators to manage user passwords.
def admin_change_password(user, password, notify=True):
"""
Administrative function to change user's password.
Parameters:
- user: User object whose password to change
- password: New plain text password
- notify: Whether to send notification email (default: True)
Returns:
True if password changed successfully, False otherwise
"""Functions for validating password strength and security requirements.
def password_length_validator(password, min_length=8, max_length=128):
"""
Validate password length requirements.
Parameters:
- password: Password to validate
- min_length: Minimum password length (default: 8)
- max_length: Maximum password length (default: 128)
Returns:
True if valid, False otherwise
Raises:
ValidationError: If password length is invalid
"""
def password_complexity_validator(password):
"""
Validate password complexity requirements.
Parameters:
- password: Password to validate
Returns:
True if password meets complexity requirements
Raises:
ValidationError: If password doesn't meet complexity requirements
"""
def password_breached_validator(password):
"""
Check if password appears in known breach databases.
Parameters:
- password: Password to check
Returns:
True if password is safe to use
Raises:
ValidationError: If password is found in breach database
"""
def pwned(password):
"""
Check if password appears in HaveIBeenPwned database.
Parameters:
- password: Password to check
Returns:
Number of times password appears in breaches (0 if safe)
"""Utility class for advanced password management operations.
class PasswordUtil:
"""
Utility class for password management operations.
"""
def __init__(self, app=None):
"""Initialize password utility with Flask app."""
def hash_password(self, password):
"""Hash password using configured algorithm."""
def verify_password(self, password, password_hash):
"""Verify password against hash."""
def generate_password(self, length=12, include_symbols=True):
"""Generate secure random password."""
def check_password_strength(self, password):
"""Analyze password strength and return score."""from flask_security import current_user, hash_password
@app.route('/change-password', methods=['GET', 'POST'])
@login_required
def change_password():
form = ChangePasswordForm()
if form.validate_on_submit():
if verify_password(form.password.data, current_user.password):
current_user.password = hash_password(form.new_password.data)
db.session.commit()
flash('Password changed successfully')
return redirect('/profile')
else:
flash('Current password is incorrect')
return render_template('change_password.html', form=form)from flask_security import send_reset_password_instructions, reset_password_token_status
@app.route('/forgot-password', methods=['GET', 'POST'])
def forgot_password():
form = ForgotPasswordForm()
if form.validate_on_submit():
user = lookup_identity(form.user.data)
if user:
send_reset_password_instructions(user)
# Always show success message for security
flash('If your email is registered, you will receive reset instructions')
return redirect('/login')
return render_template('forgot_password.html', form=form)
@app.route('/reset-password/<token>', methods=['GET', 'POST'])
def reset_password(token):
expired, invalid, user = reset_password_token_status(token)
if expired:
flash('The reset link has expired')
return redirect('/forgot-password')
if invalid or not user:
flash('Invalid reset link')
return redirect('/login')
form = ResetPasswordForm()
if form.validate_on_submit():
update_password(user, form.password.data)
db.session.commit()
flash('Password reset successfully')
return redirect('/login')
return render_template('reset_password.html', form=form, token=token)from flask_security import admin_change_password
@app.route('/admin/reset-user-password/<int:user_id>', methods=['POST'])
@roles_required('admin')
def admin_reset_password(user_id):
user = User.query.get_or_404(user_id)
new_password = request.form.get('new_password')
if admin_change_password(user, new_password, notify=True):
db.session.commit()
flash(f'Password reset for user {user.email}')
else:
flash('Failed to reset password')
return redirect(f'/admin/user/{user_id}')from flask_security import password_length_validator, password_complexity_validator
from wtforms.validators import ValidationError
class StrongPasswordForm(Form):
password = PasswordField('Password', validators=[
DataRequired(),
Length(min=12, message='Password must be at least 12 characters'),
password_complexity_validator,
password_breached_validator
])
def validate_password(self, field):
# Custom password validation
password = field.data
# Check for common patterns
if password.lower() in ['password', '123456', 'qwerty']:
raise ValidationError('Password is too common')
# Require mixed case, numbers, and symbols
has_upper = any(c.isupper() for c in password)
has_lower = any(c.islower() for c in password)
has_digit = any(c.isdigit() for c in password)
has_symbol = any(c in '!@#$%^&*()' for c in password)
if not all([has_upper, has_lower, has_digit, has_symbol]):
raise ValidationError(
'Password must contain uppercase, lowercase, numbers, and symbols'
)from flask_security import pwned
@app.route('/check-password-safety', methods=['POST'])
@login_required
def check_password_safety():
password = request.form.get('password')
try:
breach_count = pwned(password)
if breach_count > 0:
return jsonify({
'safe': False,
'message': f'Password found in {breach_count} data breaches',
'recommendation': 'Choose a different password'
})
else:
return jsonify({
'safe': True,
'message': 'Password not found in known breaches'
})
except Exception as e:
return jsonify({
'safe': None,
'message': 'Unable to check password safety',
'error': str(e)
})from flask_security import Security
from passlib.context import CryptContext
# Custom password context with specific algorithms
pwd_context = CryptContext(
schemes=['bcrypt', 'argon2'],
default='argon2',
bcrypt__min_rounds=12,
argon2__time_cost=16,
argon2__memory_cost=102400,
argon2__parallelism=8
)
app.config['SECURITY_PASSWORD_HASH'] = 'argon2'
app.config['SECURITY_PASSWORD_LENGTH_MIN'] = 12
app.config['SECURITY_PASSWORD_COMPLEXITY_CHECKER'] = 'zxcvbn'
app.config['SECURITY_PASSWORD_CHECK_BREACHED'] = True
app.config['SECURITY_PASSWORD_BREACHED_COUNT'] = 1
security = Security(app, user_datastore)class UserWithPasswordHistory(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
password_history = db.Column(db.Text) # JSON field for password history
def add_password_to_history(self, password_hash):
"""Add password hash to history."""
history = json.loads(self.password_history or '[]')
history.append({
'hash': password_hash,
'created_at': datetime.utcnow().isoformat()
})
# Keep only last 5 passwords
self.password_history = json.dumps(history[-5:])
def is_password_in_history(self, password):
"""Check if password was used previously."""
history = json.loads(self.password_history or '[]')
return any(
verify_password(password, entry['hash'])
for entry in history
)
# Custom password change with history check
@app.route('/secure-change-password', methods=['POST'])
@login_required
def secure_change_password():
new_password = request.form.get('new_password')
if current_user.is_password_in_history(new_password):
flash('Cannot reuse a recent password')
return redirect('/change-password')
# Add current password to history before changing
current_user.add_password_to_history(current_user.password)
current_user.password = hash_password(new_password)
db.session.commit()
flash('Password changed successfully')
return redirect('/profile')Key configuration variables for password management:
# Password hashing
app.config['SECURITY_PASSWORD_HASH'] = 'bcrypt' # or 'argon2', 'pbkdf2_sha512'
app.config['SECURITY_PASSWORD_SALT'] = 'your-secret-salt'
# Password validation
app.config['SECURITY_PASSWORD_LENGTH_MIN'] = 8
app.config['SECURITY_PASSWORD_LENGTH_MAX'] = 128
app.config['SECURITY_PASSWORD_COMPLEXITY_CHECKER'] = 'zxcvbn'
app.config['SECURITY_PASSWORD_CHECK_BREACHED'] = True
app.config['SECURITY_PASSWORD_BREACHED_COUNT'] = 1
# Password reset
app.config['SECURITY_RECOVERABLE'] = True
app.config['SECURITY_RESET_PASSWORD_WITHIN'] = '5 days'
app.config['SECURITY_RESET_URL'] = '/reset-password'
# Change password
app.config['SECURITY_CHANGEABLE'] = True
app.config['SECURITY_CHANGE_URL'] = '/change-password'
app.config['SECURITY_POST_CHANGE_REDIRECT_ENDPOINT'] = '/profile'
# Send password change notice
app.config['SECURITY_SEND_PASSWORD_CHANGE_EMAIL'] = True
app.config['SECURITY_SEND_PASSWORD_RESET_NOTICE_EMAIL'] = True