0
# Password Management
1
2
Password hashing, validation, reset workflows, security utilities, and password policy enforcement for managing user credentials securely in Flask applications.
3
4
## Capabilities
5
6
### Password Hashing and Verification
7
8
Core functions for secure password storage and verification using modern hashing algorithms.
9
10
```python { .api }
11
def hash_password(password):
12
"""
13
Hash a password using the configured hashing algorithm.
14
15
Parameters:
16
- password: Plain text password to hash
17
18
Returns:
19
Hashed password string suitable for database storage
20
"""
21
22
def verify_password(password, password_hash):
23
"""
24
Verify a password against its stored hash.
25
26
Parameters:
27
- password: Plain text password to verify
28
- password_hash: Stored password hash
29
30
Returns:
31
True if password matches hash, False otherwise
32
"""
33
34
def verify_and_update_password(password, user):
35
"""
36
Verify password and update hash if algorithm upgrade is needed.
37
38
Parameters:
39
- password: Plain text password to verify
40
- user: User object with password hash
41
42
Returns:
43
True if password matches, False otherwise
44
"""
45
```
46
47
### Password Change Forms
48
49
Forms for users to change their existing passwords.
50
51
```python { .api }
52
class ChangePasswordForm(Form):
53
"""
54
Form for users to change their current password.
55
56
Fields:
57
- password: Current password field for verification
58
- new_password: New password field
59
- new_password_confirm: New password confirmation field
60
- submit: Submit button
61
"""
62
password: PasswordField
63
new_password: PasswordField
64
new_password_confirm: PasswordField
65
submit: SubmitField
66
```
67
68
### Password Reset Forms
69
70
Forms for password recovery workflow when users forget their passwords.
71
72
```python { .api }
73
class ForgotPasswordForm(Form):
74
"""
75
Form for requesting password reset instructions.
76
77
Fields:
78
- user: Email or username field for identity lookup
79
- submit: Submit button
80
"""
81
user: StringField
82
submit: SubmitField
83
84
class ResetPasswordForm(Form):
85
"""
86
Form for setting new password during reset process.
87
88
Fields:
89
- password: New password field
90
- password_confirm: New password confirmation field
91
- submit: Submit button
92
"""
93
password: PasswordField
94
password_confirm: PasswordField
95
submit: SubmitField
96
```
97
98
### Password Reset Functions
99
100
Functions for managing the password reset workflow and token validation.
101
102
```python { .api }
103
def send_reset_password_instructions(user):
104
"""
105
Send password reset instructions via email.
106
107
Parameters:
108
- user: User object to send reset instructions to
109
110
Returns:
111
True if email sent successfully, False otherwise
112
"""
113
114
def send_password_reset_notice(user):
115
"""
116
Send notification that password was reset (security notice).
117
118
Parameters:
119
- user: User object whose password was reset
120
121
Returns:
122
True if email sent successfully, False otherwise
123
"""
124
125
def generate_reset_password_token(user):
126
"""
127
Generate password reset token for user.
128
129
Parameters:
130
- user: User object to generate token for
131
132
Returns:
133
Reset token string
134
"""
135
136
def reset_password_token_status(token):
137
"""
138
Validate password reset token and return status information.
139
140
Parameters:
141
- token: Reset token to validate
142
143
Returns:
144
Tuple of (expired: bool, invalid: bool, user: User|None)
145
"""
146
147
def update_password(user, password):
148
"""
149
Update user's password with proper hashing and validation.
150
151
Parameters:
152
- user: User object to update password for
153
- password: New plain text password
154
155
Returns:
156
True if password updated successfully, False otherwise
157
"""
158
```
159
160
### Administrative Password Functions
161
162
Functions for administrators to manage user passwords.
163
164
```python { .api }
165
def admin_change_password(user, password, notify=True):
166
"""
167
Administrative function to change user's password.
168
169
Parameters:
170
- user: User object whose password to change
171
- password: New plain text password
172
- notify: Whether to send notification email (default: True)
173
174
Returns:
175
True if password changed successfully, False otherwise
176
"""
177
```
178
179
### Password Validation Functions
180
181
Functions for validating password strength and security requirements.
182
183
```python { .api }
184
def password_length_validator(password, min_length=8, max_length=128):
185
"""
186
Validate password length requirements.
187
188
Parameters:
189
- password: Password to validate
190
- min_length: Minimum password length (default: 8)
191
- max_length: Maximum password length (default: 128)
192
193
Returns:
194
True if valid, False otherwise
195
196
Raises:
197
ValidationError: If password length is invalid
198
"""
199
200
def password_complexity_validator(password):
201
"""
202
Validate password complexity requirements.
203
204
Parameters:
205
- password: Password to validate
206
207
Returns:
208
True if password meets complexity requirements
209
210
Raises:
211
ValidationError: If password doesn't meet complexity requirements
212
"""
213
214
def password_breached_validator(password):
215
"""
216
Check if password appears in known breach databases.
217
218
Parameters:
219
- password: Password to check
220
221
Returns:
222
True if password is safe to use
223
224
Raises:
225
ValidationError: If password is found in breach database
226
"""
227
228
def pwned(password):
229
"""
230
Check if password appears in HaveIBeenPwned database.
231
232
Parameters:
233
- password: Password to check
234
235
Returns:
236
Number of times password appears in breaches (0 if safe)
237
"""
238
```
239
240
### Password Utility Class
241
242
Utility class for advanced password management operations.
243
244
```python { .api }
245
class PasswordUtil:
246
"""
247
Utility class for password management operations.
248
"""
249
250
def __init__(self, app=None):
251
"""Initialize password utility with Flask app."""
252
253
def hash_password(self, password):
254
"""Hash password using configured algorithm."""
255
256
def verify_password(self, password, password_hash):
257
"""Verify password against hash."""
258
259
def generate_password(self, length=12, include_symbols=True):
260
"""Generate secure random password."""
261
262
def check_password_strength(self, password):
263
"""Analyze password strength and return score."""
264
```
265
266
## Usage Examples
267
268
### Basic Password Change
269
270
```python
271
from flask_security import current_user, hash_password
272
273
@app.route('/change-password', methods=['GET', 'POST'])
274
@login_required
275
def change_password():
276
form = ChangePasswordForm()
277
if form.validate_on_submit():
278
if verify_password(form.password.data, current_user.password):
279
current_user.password = hash_password(form.new_password.data)
280
db.session.commit()
281
flash('Password changed successfully')
282
return redirect('/profile')
283
else:
284
flash('Current password is incorrect')
285
286
return render_template('change_password.html', form=form)
287
```
288
289
### Password Reset Workflow
290
291
```python
292
from flask_security import send_reset_password_instructions, reset_password_token_status
293
294
@app.route('/forgot-password', methods=['GET', 'POST'])
295
def forgot_password():
296
form = ForgotPasswordForm()
297
if form.validate_on_submit():
298
user = lookup_identity(form.user.data)
299
if user:
300
send_reset_password_instructions(user)
301
# Always show success message for security
302
flash('If your email is registered, you will receive reset instructions')
303
return redirect('/login')
304
305
return render_template('forgot_password.html', form=form)
306
307
@app.route('/reset-password/<token>', methods=['GET', 'POST'])
308
def reset_password(token):
309
expired, invalid, user = reset_password_token_status(token)
310
311
if expired:
312
flash('The reset link has expired')
313
return redirect('/forgot-password')
314
315
if invalid or not user:
316
flash('Invalid reset link')
317
return redirect('/login')
318
319
form = ResetPasswordForm()
320
if form.validate_on_submit():
321
update_password(user, form.password.data)
322
db.session.commit()
323
flash('Password reset successfully')
324
return redirect('/login')
325
326
return render_template('reset_password.html', form=form, token=token)
327
```
328
329
### Administrative Password Management
330
331
```python
332
from flask_security import admin_change_password
333
334
@app.route('/admin/reset-user-password/<int:user_id>', methods=['POST'])
335
@roles_required('admin')
336
def admin_reset_password(user_id):
337
user = User.query.get_or_404(user_id)
338
new_password = request.form.get('new_password')
339
340
if admin_change_password(user, new_password, notify=True):
341
db.session.commit()
342
flash(f'Password reset for user {user.email}')
343
else:
344
flash('Failed to reset password')
345
346
return redirect(f'/admin/user/{user_id}')
347
```
348
349
### Password Strength Validation
350
351
```python
352
from flask_security import password_length_validator, password_complexity_validator
353
from wtforms.validators import ValidationError
354
355
class StrongPasswordForm(Form):
356
password = PasswordField('Password', validators=[
357
DataRequired(),
358
Length(min=12, message='Password must be at least 12 characters'),
359
password_complexity_validator,
360
password_breached_validator
361
])
362
363
def validate_password(self, field):
364
# Custom password validation
365
password = field.data
366
367
# Check for common patterns
368
if password.lower() in ['password', '123456', 'qwerty']:
369
raise ValidationError('Password is too common')
370
371
# Require mixed case, numbers, and symbols
372
has_upper = any(c.isupper() for c in password)
373
has_lower = any(c.islower() for c in password)
374
has_digit = any(c.isdigit() for c in password)
375
has_symbol = any(c in '!@#$%^&*()' for c in password)
376
377
if not all([has_upper, has_lower, has_digit, has_symbol]):
378
raise ValidationError(
379
'Password must contain uppercase, lowercase, numbers, and symbols'
380
)
381
```
382
383
### Password Breach Checking
384
385
```python
386
from flask_security import pwned
387
388
@app.route('/check-password-safety', methods=['POST'])
389
@login_required
390
def check_password_safety():
391
password = request.form.get('password')
392
393
try:
394
breach_count = pwned(password)
395
if breach_count > 0:
396
return jsonify({
397
'safe': False,
398
'message': f'Password found in {breach_count} data breaches',
399
'recommendation': 'Choose a different password'
400
})
401
else:
402
return jsonify({
403
'safe': True,
404
'message': 'Password not found in known breaches'
405
})
406
except Exception as e:
407
return jsonify({
408
'safe': None,
409
'message': 'Unable to check password safety',
410
'error': str(e)
411
})
412
```
413
414
### Custom Password Policy
415
416
```python
417
from flask_security import Security
418
from passlib.context import CryptContext
419
420
# Custom password context with specific algorithms
421
pwd_context = CryptContext(
422
schemes=['bcrypt', 'argon2'],
423
default='argon2',
424
bcrypt__min_rounds=12,
425
argon2__time_cost=16,
426
argon2__memory_cost=102400,
427
argon2__parallelism=8
428
)
429
430
app.config['SECURITY_PASSWORD_HASH'] = 'argon2'
431
app.config['SECURITY_PASSWORD_LENGTH_MIN'] = 12
432
app.config['SECURITY_PASSWORD_COMPLEXITY_CHECKER'] = 'zxcvbn'
433
app.config['SECURITY_PASSWORD_CHECK_BREACHED'] = True
434
app.config['SECURITY_PASSWORD_BREACHED_COUNT'] = 1
435
436
security = Security(app, user_datastore)
437
```
438
439
### Password History Prevention
440
441
```python
442
class UserWithPasswordHistory(UserMixin, db.Model):
443
id = db.Column(db.Integer, primary_key=True)
444
email = db.Column(db.String(255), unique=True)
445
password = db.Column(db.String(255))
446
password_history = db.Column(db.Text) # JSON field for password history
447
448
def add_password_to_history(self, password_hash):
449
"""Add password hash to history."""
450
history = json.loads(self.password_history or '[]')
451
history.append({
452
'hash': password_hash,
453
'created_at': datetime.utcnow().isoformat()
454
})
455
# Keep only last 5 passwords
456
self.password_history = json.dumps(history[-5:])
457
458
def is_password_in_history(self, password):
459
"""Check if password was used previously."""
460
history = json.loads(self.password_history or '[]')
461
return any(
462
verify_password(password, entry['hash'])
463
for entry in history
464
)
465
466
# Custom password change with history check
467
@app.route('/secure-change-password', methods=['POST'])
468
@login_required
469
def secure_change_password():
470
new_password = request.form.get('new_password')
471
472
if current_user.is_password_in_history(new_password):
473
flash('Cannot reuse a recent password')
474
return redirect('/change-password')
475
476
# Add current password to history before changing
477
current_user.add_password_to_history(current_user.password)
478
current_user.password = hash_password(new_password)
479
db.session.commit()
480
481
flash('Password changed successfully')
482
return redirect('/profile')
483
```
484
485
## Configuration Options
486
487
Key configuration variables for password management:
488
489
```python
490
# Password hashing
491
app.config['SECURITY_PASSWORD_HASH'] = 'bcrypt' # or 'argon2', 'pbkdf2_sha512'
492
app.config['SECURITY_PASSWORD_SALT'] = 'your-secret-salt'
493
494
# Password validation
495
app.config['SECURITY_PASSWORD_LENGTH_MIN'] = 8
496
app.config['SECURITY_PASSWORD_LENGTH_MAX'] = 128
497
app.config['SECURITY_PASSWORD_COMPLEXITY_CHECKER'] = 'zxcvbn'
498
app.config['SECURITY_PASSWORD_CHECK_BREACHED'] = True
499
app.config['SECURITY_PASSWORD_BREACHED_COUNT'] = 1
500
501
# Password reset
502
app.config['SECURITY_RECOVERABLE'] = True
503
app.config['SECURITY_RESET_PASSWORD_WITHIN'] = '5 days'
504
app.config['SECURITY_RESET_URL'] = '/reset-password'
505
506
# Change password
507
app.config['SECURITY_CHANGEABLE'] = True
508
app.config['SECURITY_CHANGE_URL'] = '/change-password'
509
app.config['SECURITY_POST_CHANGE_REDIRECT_ENDPOINT'] = '/profile'
510
511
# Send password change notice
512
app.config['SECURITY_SEND_PASSWORD_CHANGE_EMAIL'] = True
513
app.config['SECURITY_SEND_PASSWORD_RESET_NOTICE_EMAIL'] = True
514
```