Skip to main content

Flask Password Hashing

Introduction

When building web applications, storing user passwords securely is critical. If your database is ever compromised, you don't want attackers to directly see user passwords. This is where password hashing comes in.

Password hashing converts plain text passwords into scrambled strings that cannot be reversed back to the original password. In this tutorial, we'll learn how to implement password hashing in Flask applications using the werkzeug.security module, which comes built-in with Flask.

What is Password Hashing?

Password hashing is a one-way transformation of a password into a fixed-length string of characters. Unlike encryption, hashing is designed to be non-reversible - you can convert a password into a hash, but you cannot convert the hash back to the original password.

When a user creates an account, we hash their password before storing it in our database. Later, when the user tries to log in, we hash the password they provide and compare it with the stored hash. If they match, the user entered the correct password.

Setting Up a Flask Project

First, let's set up a basic Flask project:

bash
mkdir flask_password_hashing
cd flask_password_hashing
python -m venv venv
# On Windows: venv\Scripts\activate
# On macOS/Linux: source venv/bin/activate
pip install flask flask-sqlalchemy

Now, create a file called app.py with the following code:

python
from flask import Flask, render_template, request, redirect, url_for, flash
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

# User model
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password_hash = db.Column(db.String(128))

def set_password(self, password):
self.password_hash = generate_password_hash(password)

def check_password(self, password):
return check_password_hash(self.password_hash, password)

def __repr__(self):
return f'<User {self.username}>'

# Create database tables
with app.app_context():
db.create_all()

Understanding the Code

Let's break down the key components:

  1. Imports: We import generate_password_hash and check_password_hash from werkzeug.security.

  2. User Model: We define a User model with a password_hash field instead of storing the password directly.

  3. set_password Method: This method takes a plain text password and stores its hash.

  4. check_password Method: This method verifies if a provided password matches the stored hash.

Creating Routes for Registration and Login

Now let's add routes for user registration and login:

python
@app.route('/')
def index():
return render_template('index.html')

@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']

# Check if user already exists
existing_user = User.query.filter_by(username=username).first()
if existing_user:
flash('Username already exists')
return redirect(url_for('register'))

# Create new user
new_user = User(username=username)
new_user.set_password(password)

# Add user to database
db.session.add(new_user)
db.session.commit()

flash('Registration successful! Please log in.')
return redirect(url_for('login'))

return render_template('register.html')

@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']

# Find user by username
user = User.query.filter_by(username=username).first()

# Check if user exists and password is correct
if user and user.check_password(password):
flash('Login successful!')
return redirect(url_for('index'))
else:
flash('Invalid username or password')
return redirect(url_for('login'))

return render_template('login.html')

if __name__ == '__main__':
app.run(debug=True)

Creating Templates

For our application to work, we need to create HTML templates. First, create a directory called templates:

bash
mkdir templates

index.html

html
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>Welcome to the Flask Password Hashing Demo</h1>

{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}

<a href="{{ url_for('register') }}">Register</a> |
<a href="{{ url_for('login') }}">Login</a>
</body>
</html>

register.html

html
<!DOCTYPE html>
<html>
<head>
<title>Register</title>
</head>
<body>
<h1>Register</h1>

{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}

<form method="POST">
<div>
<label>Username</label>
<input type="text" name="username" required>
</div>
<div>
<label>Password</label>
<input type="password" name="password" required>
</div>
<button type="submit">Register</button>
</form>

<p>Already have an account? <a href="{{ url_for('login') }}">Login</a></p>
</body>
</html>

login.html

html
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>

{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}

<form method="POST">
<div>
<label>Username</label>
<input type="text" name="username" required>
</div>
<div>
<label>Password</label>
<input type="password" name="password" required>
</div>
<button type="submit">Login</button>
</form>

<p>Don't have an account? <a href="{{ url_for('register') }}">Register</a></p>
</body>
</html>

How Password Hashing Works

When we call generate_password_hash(password), Werkzeug:

  1. Takes the plain password as input
  2. Adds a random "salt" to make the hash unique even for identical passwords
  3. Applies a hashing algorithm (default is SHA-256)
  4. Returns a string that contains the salt, algorithm, and hash

For example, if we hash the password "password123", it might look like:

pbkdf2:sha256:150000$FLQSrCVn$a9b773d6944f5478ab590a417220183f5a77a951c392548f5303e21ef9b3bac1

This string includes:

  • The hashing method (pbkdf2:sha256)
  • The number of iterations (150000)
  • The salt (FLQSrCVn)
  • The actual hash

When we later want to verify a password, check_password_hash(stored_hash, provided_password):

  1. Extracts the hashing method, salt, and iterations from the stored hash
  2. Hashes the provided password using the same method, salt, and iterations
  3. Compares the resulting hash with the stored hash
  4. Returns True if they match, False otherwise

Real-World Example: User Profile with Password Change

Let's add a profile page where users can change their password:

python
@app.route('/profile', methods=['GET', 'POST'])
def profile():
# In a real app, you would get the current user from a session
# For this example, we'll just use the first user in the database
user = User.query.first()

if not user:
flash('No users exist in the system')
return redirect(url_for('register'))

if request.method == 'POST':
current_password = request.form['current_password']
new_password = request.form['new_password']

# Verify current password
if user.check_password(current_password):
# Set new password
user.set_password(new_password)
db.session.commit()
flash('Password updated successfully!')
else:
flash('Current password is incorrect')

return render_template('profile.html', username=user.username)

Create a new template profile.html:

html
<!DOCTYPE html>
<html>
<head>
<title>Profile</title>
</head>
<body>
<h1>Profile for {{ username }}</h1>

{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}

<h2>Change Password</h2>
<form method="POST">
<div>
<label>Current Password</label>
<input type="password" name="current_password" required>
</div>
<div>
<label>New Password</label>
<input type="password" name="new_password" required>
</div>
<button type="submit">Update Password</button>
</form>

<p><a href="{{ url_for('index') }}">Back to Home</a></p>
</body>
</html>

Best Practices for Password Hashing

  1. Never store plain text passwords: Always hash passwords before storing them.

  2. Use strong hashing algorithms: Werkzeug defaults to pbkdf2:sha256, which is currently considered secure.

  3. Include a salt: Werkzeug automatically adds a salt to make rainbow table attacks more difficult.

  4. Keep your SECRET_KEY secure: In production, use a strong, random SECRET_KEY and don't commit it to version control.

  5. Consider using Flask extensions: For more advanced authentication needs, consider using extensions like Flask-Login and Flask-Security.

Example: Adding Password Complexity Requirements

You can add validation to ensure users create strong passwords:

python
import re

def is_password_strong(password):
"""
Check if password meets minimum security requirements:
- At least 8 characters
- Contains at least one uppercase letter
- Contains at least one lowercase letter
- Contains at least one digit
"""
if len(password) < 8:
return False
if not re.search(r'[A-Z]', password):
return False
if not re.search(r'[a-z]', password):
return False
if not re.search(r'[0-9]', password):
return False
return True

@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']

# Check password strength
if not is_password_strong(password):
flash('Password must be at least 8 characters and contain uppercase, lowercase, and a number')
return redirect(url_for('register'))

# Rest of the registration code
# ...

Summary

In this tutorial, we:

  1. Learned why password hashing is essential for security
  2. Set up a Flask application with password hashing
  3. Created user registration and login functionality using hashed passwords
  4. Added a profile page for changing passwords
  5. Explored best practices for password security

Password hashing is a fundamental security practice for any application that stores user credentials. By following the techniques in this tutorial, you can ensure that your Flask application handles passwords securely.

Additional Resources

Exercises

  1. Modify the application to include password confirmation during registration
  2. Implement a "forgot password" feature
  3. Add more password complexity requirements (e.g., special characters)
  4. Create a user dashboard that shows when the password was last changed
  5. Implement account lockout after multiple failed login attempts

By completing this tutorial and exercises, you'll have a solid understanding of how to implement secure password handling in your Flask applications.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)