Skip to main content

Flask: Password Hashing with Flask-Bcrypt

Flask: Password Hashing with Flask-Bcrypt

Password hashing is a critical security practice in Flask web applications to protect user credentials. Using Flask-Bcrypt, Flask integrates seamlessly with Flask Authentication, Flask User Registration, Flask User Login and Logout, Flask Querying the Database, and Flask Relationships in Models to securely store and verify passwords. This tutorial explores Flask password hashing with Flask-Bcrypt, covering secure password storage, verification, and integration with authentication workflows, with practical applications in web development.


01. Why Use Flask-Bcrypt for Password Hashing?

Storing plain-text passwords is a major security risk, as data breaches can expose user credentials. Flask-Bcrypt uses the bcrypt algorithm to generate secure, salted password hashes, ensuring that even if a database is compromised, passwords remain protected. Combined with Flask’s lightweight framework and NumPy Array Operations for efficient database interactions, Flask-Bcrypt provides a robust solution for secure user authentication in applications like blogs, e-commerce platforms, or dashboards.

Example: Basic Password Hashing

from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///hash.db'
app.config['SECRET_KEY'] = 'your-secret-key'
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)

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(120), nullable=False)

@app.route('/register', methods=['POST'])
def register():
    username = request.form['username']
    password = request.form['password']
    hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
    user = User(username=username, password_hash=hashed_password)
    db.session.add(user)
    db.session.commit()
    return "User registered with hashed password"

with app.app_context():
    db.create_all()

Output:

User registered with hashed password (on valid POST request)

Explanation:

  • bcrypt.generate_password_hash - Creates a secure, salted hash of the password.
  • decode('utf-8') - Converts the hash to a string for database storage.

02. Key Password Hashing Techniques

Flask-Bcrypt provides essential methods for secure password hashing and verification. Below is a summary of key techniques and their applications in web applications:

Technique Description Use Case
Password Hashing Generate secure password hashes Store passwords during registration
Password Verification Check passwords against hashes Authenticate users during login
Integration with Models Store hashes in database models Secure user data
Validation Ensure strong passwords Enforce password policies
Error Handling Manage hashing/verification errors Prevent security vulnerabilities


2.1 Password Hashing

Example: Hashing Passwords During Registration

from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///register.db'
app.config['SECRET_KEY'] = 'your-secret-key'
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)

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(120), nullable=False)

@app.route('/register', methods=['POST'])
def register():
    username = request.form['username']
    password = request.form['password']
    if User.query.filter_by(username=username).first():
        return "Username already exists"
    hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
    user = User(username=username, password_hash=hashed_password)
    db.session.add(user)
    db.session.commit()
    return "Registration successful"

with app.app_context():
    db.create_all()

Output:

Registration successful (on valid POST request with unique username)

Explanation:

  • generate_password_hash - Produces a bcrypt hash with a random salt.
  • Checks for duplicate usernames before hashing.

2.2 Password Verification

Example: Verifying Passwords During Login

from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user
from flask_bcrypt import Bcrypt

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///login.db'
app.config['SECRET_KEY'] = 'your-secret-key'
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)
login_manager = LoginManager(app)

class User(UserMixin, 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(120), nullable=False)

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']
    user = User.query.filter_by(username=username).first()
    if user and bcrypt.check_password_hash(user.password_hash, password):
        login_user(user)
        return "Login successful"
    return "Invalid credentials"

with app.app_context():
    db.create_all()
    hashed_password = bcrypt.generate_password_hash('securepass').decode('utf-8')
    db.session.add(User(username='Alice', password_hash=hashed_password))
    db.session.commit()

Output:

Login successful (on valid POST request with username='Alice' and password='securepass')

Explanation:

  • check_password_hash - Compares the provided password with the stored hash.
  • Integrates with Flask User Login and Logout for secure authentication.

2.3 Integration with Models

Example: Storing Hashes in Models

from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///models.db'
app.config['SECRET_KEY'] = 'your-secret-key'
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password_hash = db.Column(db.String(120), nullable=False)
    posts = db.relationship('Post', backref='author', lazy=True)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

@app.route('/register', methods=['POST'])
def register():
    username = request.form['username']
    email = request.form['email']
    password = request.form['password']
    if User.query.filter_by(username=username).first() or User.query.filter_by(email=email).first():
        return "Username or email already exists"
    hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
    user = User(username=username, email=email, password_hash=hashed_password)
    db.session.add(user)
    db.session.commit()
    return "Registered"

with app.app_context():
    db.create_all()

Output:

Registered (on valid POST request with unique username and email)

Explanation:

  • password_hash - Stores the bcrypt hash in the User model.
  • Integrates with Flask Relationships in Models to link users to posts.

2.4 Password Validation

Example: Enforcing Strong Passwords

from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///validate.db'
app.config['SECRET_KEY'] = 'your-secret-key'
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)

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(120), nullable=False)

@app.route('/register', methods=['POST'])
def register():
    username = request.form['username']
    password = request.form['password']
    if not password or len(password) < 8:
        return "Password must be at least 8 characters"
    if User.query.filter_by(username=username).first():
        return "Username already exists"
    hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
    user = User(username=username, password_hash=hashed_password)
    db.session.add(user)
    db.session.commit()
    return "Registration successful"

with app.app_context():
    db.create_all()

Output:

Registration successful (on valid POST request with password >= 8 characters)

Explanation:

  • Enforces a minimum password length to ensure stronger passwords.
  • Combines with Flask User Registration for secure onboarding.

2.5 Incorrect Password Handling

Example: Storing Plain-Text Passwords

from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///insecure.db'
db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password = db.Column(db.String(120), nullable=False)  # Plain text

@app.route('/register', methods=['POST'])
def register():
    username = request.form['username']
    password = request.form['password']
    user = User(username=username, password=password)  # Insecure
    db.session.add(user)
    db.session.commit()
    return "Registered"

with app.app_context():
    db.create_all()

Output:

Registered (but with insecure plain-text password)

Explanation:

  • Storing plain-text passwords exposes credentials in a breach.
  • Solution: Use Flask-Bcrypt for secure hashing.

03. Effective Usage

3.1 Recommended Practices

  • Always hash passwords with Flask-Bcrypt and verify them securely.

Example: Comprehensive Password Hashing

from flask import Flask, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user
from flask_bcrypt import Bcrypt

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///complete.db'
app.config['SECRET_KEY'] = 'secure-secret-key'
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password_hash = db.Column(db.String(120), nullable=False)

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

@app.route('/register', methods=['POST'])
def register():
    username = request.form['username']
    email = request.form['email']
    password = request.form['password']
    if not all([username, email, password]):
        return "All fields required"
    if len(password) < 8:
        return "Password must be at least 8 characters"
    if User.query.filter_by(username=username).first():
        return "Username taken"
    if User.query.filter_by(email=email).first():
        return "Email taken"
    hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
    user = User(username=username, email=email, password_hash=hashed_password)
    db.session.add(user)
    db.session.commit()
    return "Registered"

@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']
    user = User.query.filter_by(username=username).first()
    if user and bcrypt.check_password_hash(user.password_hash, password):
        login_user(user)
        return redirect(url_for('dashboard'))
    return "Invalid credentials"

@app.route('/dashboard')
def dashboard():
    return "User dashboard"

with app.app_context():
    db.create_all()

Output:

Registered (on valid POST request with unique username, email, and password >= 8 characters)
User dashboard (on valid login)
  • Validates inputs and enforces strong passwords.
  • Integrates hashing and verification with registration and login workflows.
  • Uses a secure secret key for session management.

3.2 Practices to Avoid

  • Avoid storing plain-text passwords or using weak hashing algorithms.

Example: Insecure Password Storage

from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
import hashlib  # Weak hashing

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///weak.db'
db = SQLAlchemy(app)

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(120), nullable=False)

@app.route('/register', methods=['POST'])
def register():
    username = request.form['username']
    password = request.form['password']
    # Insecure: Using MD5 without salt
    hashed_password = hashlib.md5(password.encode()).hexdigest()
    user = User(username=username, password_hash=hashed_password)
    db.session.add(user)
    db.session.commit()
    return "Registered"

with app.app_context():
    db.create_all()

Output:

Registered (but with insecure MD5 hashing)
  • MD5 is outdated and vulnerable to attacks.
  • Solution: Use Flask-Bcrypt for secure, salted hashing.

04. Common Use Cases in Web Development

4.1 Secure Blog Authentication

Hash and verify passwords for secure blog user authentication.

Example: Blog Password Hashing

from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
app.config['SECRET_KEY'] = 'your-secret-key'
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)

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(120), nullable=False)
    posts = db.relationship('Post', backref='author', lazy=True)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

@app.route('/register', methods=['POST'])
def register():
    username = request.form['username']
    password = request.form['password']
    if not password or len(password) < 8:
        return "Password must be at least 8 characters"
    if User.query.filter_by(username=username).first():
        return "Username taken"
    hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
    user = User(username=username, password_hash=hashed_password)
    db.session.add(user)
    db.session.commit()
    return "Registered"

with app.app_context():
    db.create_all()

Output:

Registered (on valid POST request)

Explanation:

  • Secures blog user credentials with bcrypt hashing.
  • Integrates with Flask Relationships in Models for post creation.

4.2 E-commerce User Security

Protect e-commerce user accounts with secure password hashing.

Example: E-commerce Password Hashing

from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///ecommerce.db'
app.config['SECRET_KEY'] = 'your-secret-key'
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password_hash = db.Column(db.String(120), nullable=False)
    orders = db.relationship('Order', backref='customer', lazy=True)

class Order(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    item = db.Column(db.String(100), nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

@app.route('/register', methods=['POST'])
def register():
    username = request.form['username']
    email = request.form['email']
    password = request.form['password']
    if not all([username, email, password]):
        return "All fields required"
    if len(password) < 8:
        return "Password must be at least 8 characters"
    if User.query.filter_by(username=username).first():
        return "Username taken"
    if User.query.filter_by(email=email).first():
        return "Email taken"
    hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
    user = User(username=username, email=email, password_hash=hashed_password)
    db.session.add(user)
    db.session.commit()
    return "Registered"

with app.app_context():
    db.create_all()

Output:

Registered (on valid POST request)

Explanation:

  • Secures e-commerce user accounts with bcrypt hashing.
  • Links users to orders, supporting secure account management.

Conclusion

Flask password hashing with Flask-Bcrypt, integrated with Flask Authentication, Flask User Registration, and NumPy Array Operations, ensures secure storage and verification of user credentials. Key takeaways:

  • Use Flask-Bcrypt for secure password hashing and verification.
  • Integrate with registration and login workflows for complete security.
  • Apply in blogs, e-commerce, or other secure applications.
  • Avoid plain-text passwords or weak hashing algorithms like MD5.

With these techniques, you can build Flask applications that protect user credentials and maintain trust in your web platform!

Comments