No Un-regex-tered Users

No Un-regex-tered Users

Get started using RegEx and bcrypt to protect and validate user passwords in your flask app

Introduction

We've all been there—an email pops up saying your password security has been compromised, sounding menacing and promising risks to the security of your information. Before I became a developer, when I received these emails, I often found myself wondering what went into password protection to make it so difficult to maintain. Well, as part of a recent project, I found out exactly how difficult it can be, even on a small level. When working with Flask-SQLAlchemy, a Python web framework, leveraging regular expressions (regex) and bcrypt can significantly enhance password security. In this blog post, I will explain how to combine regex patterns and the bcrypt hashing algorithm to implement robust password validation in a small Flask-SQLAlchemy project.

Intro to regex and bcrpyt:

Before diving into a code example and more detailed explanations, let me first provide a basic introduction to regex and bcrypt, as well as some key concepts, terms, and ideas.

Regex - Regex is used to compare strings to a pattern created by the developer. This allows for identifying information that doesn't meet specific parameters and creating an error. You can find a more in-depth guide to regex here.

Bcrypt - Bcrypt is a Python package used for password hashing. Hashing is a method of converting one string into another following a specific set of guidelines. Several factors make hashing ideal for password protection. First, it is a one-way translation, meaning that once a password has been hashed, it is saved as the hashed value and not the actual password. Second, hashing involves standardizing the password while still incorporating unique identifiers (this is called adding salt). You can find more information about bcrypt, hashing, and salting here.

Now that you have gotten a basic introduction it's time to dive right into an example.

Using regex and bcrypt

Before we delve into the code, let's make sure you have a working environment set up. Ensure that you have Python installed, along with Flask, SQLAlchemy, and bcrypt. You can install these dependencies using pip:

pip install flask sqlalchemy bcrypt

To start, create a new Flask project, set up the necessary files and directories, then in your project's root directory, run the following commands in your terminal:

mkdir my_project
cd my_project
touch app.py
mkdir templates
mkdir static

Open the app.py file in your favorite text editor and add the following code:

from flask import Flask, render_template, request
from flask_sqlalchemy import SQLAlchemy
from bcrypt import hashpw, gensalt
import re

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

# add some code here!

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

Creating the User Model

Next, let's define a User model to represent our user database table. Inside app.py enter the following:

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50), unique=True, nullable=False)
    password_hash = db.Column(db.String(100), nullable=False)

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

    def set_password(self, password):
        salt = gensalt()
        self.password_hash = hashpw(password.encode(), salt).decode()

    def check_password(self, password):
        return hashpw(password.encode(), self.password_hash.encode()) == self.password_hash.encode()

Implement password validations

One of the most common ways of enforcing strong password security is using Regular Expressions or regex. Move over to the User model and add a static method called validate_password(). This function will perform our password validation. Add the following code inside the User class:

@staticmethod
def validate_password(password):
    pattern = r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$"
    return re.match(pattern, password) is not None

This regex pattern enforces the following password requirements (you might recognize these requirements from almost every time you've been asked to create a password):

  • Minimum eight characters

  • At least one uppercase letter

  • At least one lowercase letter

  • At least one number

  • At least one special character from @$!%*?&

Adding to your Route(s)

Now that we have our password validation and bcrypt hashing logic ready, let's integrate them into a registration route. Add the following code inside the app.py file:

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

        if User.query.filter_by(username=username).first():
            return 'Username already exists!'

        if not User.validate_password(password):
            return 'Invalid password!'

        new_user = User(username=username)
        new_user.set_password(password)
        db.session.add(new_user)
        db.session.commit()

        return 'Registration successful!'

    return render_template('register.html')

(note - the routing can be done more efficiently using flask_restful however this is the simplest method and I won't go into RESTful routing here)

In this code, we grab the username and password from the registration form. We then check if the username is already in the database, validate the password using our regex pattern, and hash the password using bcrypt. Finally, we add the new user to the database and commit the changes.

Wrapping Up

In this blog post, I explained how to use regex and bcrypt to combine regex patterns with bcrypt for enforcing strong password requirements and protecting sensitive user information. We explored the basics of integrating password validation and bcrypt hashing in a Flask-SQLAlchemy project. However, this is just the beginning—there is always more to learn when it comes to information safety!

Happy coding, and remember to stay secure!