DEV Community

Cover image for Securing Your Flask Application: Hashing Passwords Tutorial
Akanni Modupe Adegoke
Akanni Modupe Adegoke

Posted on

Securing Your Flask Application: Hashing Passwords Tutorial

Introduction:
In this tutorial, we will explore an important aspect of Flask application security: hashing passwords. Storing passwords securely is crucial to protect user accounts and sensitive information. We'll walk through the process of hashing passwords using the Werkzeug security module in Flask, ensuring that even if your application's database is compromised, passwords remain secure. By the end of this tutorial, you'll have a strong understanding of how to implement password hashing in your Flask application.

Table of Contents:

  1. Why Password Hashing?
  2. Setting Up the Flask Application
  3. Installing Dependencies
  4. Creating a User Model
  5. Implementing Password Hashing
  6. Authenticating Users
  7. Conclusion

Section 1: Why Password Hashing?
In this section, we'll discuss the importance of password hashing and why it's necessary to protect user passwords in your Flask application. Storing passwords securely is crucial to safeguard user accounts and sensitive information.

When users create an account on your application, they typically provide a password that grants them access to their account. Storing these passwords in plain text format is highly risky because if an attacker gains unauthorized access to your application's database, they can easily retrieve and exploit these passwords.

To mitigate this risk, it's crucial to use password hashing. Password hashing is a cryptographic technique that converts a plain text password into an irreversible string of characters, known as a hash. This hash is stored in the database instead of the plain text password. When a user attempts to log in, their entered password is hashed and compared against the stored hash. If the hashes match, the password is considered valid.

By using password hashing, even if an attacker gains access to the database, they won't be able to retrieve the original passwords. Instead, they would only have access to the hashed passwords, which are computationally infeasible to reverse-engineer back to their original plain text form.

Flask provides a convenient way to implement password hashing through the Werkzeug security module, which offers robust hashing algorithms and methods for password storage and verification.

In the following sections, we'll explore how to incorporate password hashing into a Flask application and demonstrate best practices for securing user passwords.

Section 2: Setting Up the Flask Application
Before diving into password hashing, we need to set up a basic Flask application structure. This section will guide you through the process of creating the necessary files and directories for your Flask application.

Start by creating a new directory for your project. Inside that directory, create a virtual environment to isolate your application's dependencies. Activate the virtual environment, and then install Flask using pip:

$ mkdir flask-app
$ cd flask-app
$ python3 -m venv venv
$ source venv/bin/activate
$ pip install Flask

Enter fullscreen mode Exit fullscreen mode

Next, create a new Python file named app.py in your project directory. This file will serve as the entry point for your Flask application. Open app.py in a text editor and add the following code to set up a basic Flask application:

from flask import Flask, render_template

app = Flask(__name__)

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

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

Enter fullscreen mode Exit fullscreen mode

In the code snippet above, we import the Flask class from the flask module and create a new instance of it. We define a route for the root URL '/' and return a simple HTML template using the render_template function.

Now, let's create the templates directory and an index.html file inside it. In the templates directory, create a new file named index.html and add some basic HTML content:

<!DOCTYPE html>
<html>
<head>
    <title>Flask Application</title>
</head>
<body>
    <h1>Welcome to My Flask Application</h1>
</body>
</html>

Enter fullscreen mode Exit fullscreen mode

With this basic setup, you have a functioning Flask application that displays a welcome message on the home page. You can run the application by executing the app.py file:

$ python app.py

Open your web browser and visit http://localhost:5000 to see the application in action.

In the next section, we'll install the necessary dependencies for password hashing and user authentication.

Section 3: Installing Dependencies
To implement password hashing and user authentication in Flask, we need to install a few dependencies. Apart from Flask itself, we'll install Flask-WTF for form handling and Flask-SQLAlchemy for working with databases.

Activate your virtual environment if it's not already active. In your terminal, run the following commands to install the required dependencies:

$ pip install Flask-WTF Flask-SQLAlchemy

These commands will install Flask-WTF and Flask-SQLAlchemy along with their respective dependencies. Flask-WTF provides us with convenient form handling and validation capabilities, while Flask-SQLAlchemy allows us to interact with databases using an Object-Relational Mapping (ORM) approach.

With the dependencies installed, we're now ready to proceed with creating the User model and implementing password hashing.

Section 4: Creating a User Model
In this section, we'll define a User model using SQLAlchemy, which will represent users in our application. The User model will have attributes such as username, email, and password. By utilizing SQLAlchemy, we can interact with the database using Python objects.

Create a new file named models.py in your project directory. This file will contain the User model definition. Open models.py in a text editor and add the following code:

from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash

db = SQLAlchemy()

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

    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)

Enter fullscreen mode Exit fullscreen mode

In the code snippet above, we import the SQLAlchemy class from flask_sqlalchemy and the generate_password_hash and check_password_hash functions from werkzeug.security. We create an instance of SQLAlchemy named db.

The User class represents the User model in our application. It inherits from db.Model, which is the base class provided by SQLAlchemy for defining database models. Inside the User class, we define the following attributes:

id: An integer field representing the user's ID, with the primary_key=True parameter indicating that it serves as the primary key for the table.
username: A string field representing the user's username. We set unique=True to ensure that each username is unique in the database.
email: A string field representing the user's email address. Similar to username, we set unique=True to enforce uniqueness.
password_hash: A string field representing the hashed password. We store the hashed password in the database instead of the plain text password.
We define two methods inside the User class:

set_password(self, password): This method takes a plain text password as input and uses generate_password_hash from Werkzeug to hash the password and store the resulting hash in the password_hash attribute.
check_password(self, password): This method takes a plain text password as input and compares it with the stored hashed password using check_password_hash from Werkzeug. It returns True if the passwords match, indicating that the entered password is valid.
By creating the User model and incorporating password hashing functionality, we've established the foundation for secure user authentication in our Flask application. In the next section, we'll implement the user registration functionality, allowing users to create accounts and store their hashed passwords securely.

Section 5: Implementing Password Hashing
In this section, we'll focus on implementing password hashing using the Werkzeug security module. We'll explain the concepts of salt and hashing algorithms and demonstrate how to generate secure password hashes.

Werkzeug provides a generate_password_hash function that takes a plain text password as input and returns a securely hashed password. It uses a random salt, which adds an extra layer of security by ensuring that even if two users have the same password, their hashes will be different.

To incorporate password hashing into our registration process, we need to update our Flask routes and forms. Let's start by modifying the app.py file.

Open app.py in a text editor and update it as follows:

from flask import Flask, render_template, redirect, url_for
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, EqualTo
from models import db, User

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

db.init_app(app)

class RegistrationForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired()])
    email = StringField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('Register')

@app.route('/', methods=['GET', 'POST'])
def index():
    form = RegistrationForm()
    if form.validate_on_submit():
        username = form.username.data
        email = form.email.data
        password = form.password.data

        # Create a new user and set their password
        user = User(username=username, email=email)
        user.set_password(password)

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

        return redirect(url_for('login'))

    return render_template('index.html', form=form)

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

Enter fullscreen mode Exit fullscreen mode

In the code snippet above, we import additional modules required for form handling and validation: FlaskForm, StringField, PasswordField, SubmitField, and various validators from wtforms. We also import the redirect and url_for functions from Flask to handle redirects.

We define a new RegistrationForm class, which inherits from FlaskForm. Inside the class, we define fields for username, email, password, and confirm_password. Each field is associated with a corresponding label and validators to ensure the form inputs meet certain requirements.

Inside the index route, we create an instance of the RegistrationForm and pass it to the template. If the form is submitted and passes the validation checks, we retrieve the form data (username, email, and password), create a new user instance, and set their password using the set_password method we defined earlier. We then add the user to the database using SQLAlchemy's session and commit the changes.

Now that we've updated the routes and forms, let's make a few modifications to the User model in models.py to support the changes we made:

# ...

class User(db.Model):
    # ...

    def __init__(self, username, email):
        self.username = username
        self.email = email

    # ...

Enter fullscreen mode Exit fullscreen mode

In the code snippet above, we've added an init method to the User class to initialize the username and email attributes when creating a new user instance.

With these changes in place, our registration process now securely hashes the user's password before storing it in the database. However, we still need to implement user authentication to verify the entered password during the login process.

Section 6: Authenticating Users
In this section, we'll focus on user authentication, allowing users to log in using their registered credentials. We'll verify the entered password by comparing it with the hashed password stored in the database.

Let's update the app.py file to add the login functionality.

Open app.py in a text editor and update it as follows:

from flask import Flask, render_template, redirect, url_for
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email
from models import db, User

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

db.init_app(app)

class RegistrationForm(FlaskForm):
    # ...

class LoginForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired()])
    submit = SubmitField('Login')

@app.route('/', methods=['GET', 'POST'])
def index():
    # ...

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

        # Retrieve the user from the database
        user = User.query.filter_by(username=username).first()

        if user and user.check_password(password):
            return 'Logged in successfully!'
        else:
            return 'Invalid username or password.'

    return render_template('login.html', form=form)

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

Enter fullscreen mode Exit fullscreen mode

In the code snippet above, we've defined a new LoginForm class, similar to the RegistrationForm class. It contains fields for username, password, and a submit button.

Inside the /login route, we create an instance of the LoginForm and pass it to the template. If the form is submitted and passes the validation checks, we retrieve the entered username from the form and query the database to find the corresponding user. If the user exists and their password matches the entered password using the check_password method we defined earlier, we display a success message. Otherwise, we display an error message indicating invalid credentials.

Create a new template file named login.html inside the templates directory. Open login.html in a text editor and add the following content:

<!DOCTYPE html>
<html>
<head>
    <title>Login</title>
</head>
<body>
    <h1>Login</h1>
    <form method="POST">
        {{ form.hidden_tag() }}
        <div>
            {{ form.username.label }}
            {{ form.username() }}
        </div>
        <div>
            {{ form.password.label }}
            {{ form.password() }}
        </div>
        <div>
            {{ form.submit() }}
        </div>
    </form>
</body>
</html>

Enter fullscreen mode Exit fullscreen mode

In the template, we render the form fields and labels using the form..label and form.() syntax.

With the login functionality in place, users can now authenticate using their registered credentials. We've successfully implemented password hashing and user authentication in our Flask application.

Section 8: Conclusion
In this tutorial, we explored the importance of password hashing and learned how to implement it in a Flask application using the Werkzeug security module. We discussed the risks associated with storing plain text passwords and demonstrated how hashing addresses those risks.

We started by setting up the Flask application, installing the necessary dependencies, and creating a User model using SQLAlchemy. We then implemented password hashing by utilizing the generate_password_hash and check_password_hash functions from Werkzeug. The User model was updated to incorporate the set_password and check_password methods.

Next, we implemented user registration and authentication functionality. During registration, passwords were securely hashed before storing them in the database. During login, the entered password was compared with the hashed password to verify the user's credentials.

It's important to note that security is a complex and evolving field, and this tutorial covers only one aspect of securing a Flask application. To ensure comprehensive security, it's crucial to consider other measures such as protection against SQL injection, cross-site scripting (XSS), and other common vulnerabilities.

Remember to stay updated on the latest security best practices, keep your application's dependencies and libraries up to date, and regularly review and improve your security measures.

With the knowledge gained from this tutorial, you have a solid foundation for implementing password hashing and user authentication in your Flask application.

Top comments (0)