Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 35

Password Hashing with `passlib` vs. Django

Recommended resources

Hello! Welcome to the first lesson in our module on Authentication and Authorization.

In the previous module, we mastered FastAPI's dependency injection system, including how to apply dependencies at different scopes to run logic for specific endpoints, routers, or your entire application. This is a crucial skill for building secure APIs, as it allows us to centralize and enforce security rules without duplicating code.

Today, we will begin building our security layer by tackling the most fundamental aspect of user authentication: password management. Our learning outcome is to implement password hashing and verification using passlib, compared to Django's built-in auth hashers.

Coming from Django, you'll be familiar with how its authentication system handles password security. When you use user.set_password('raw_password'), Django automatically hashes the password using a configured algorithm (like PBKDF2 or Argon2). Later, user.check_password('raw_password') verifies a login attempt. This process is largely abstracted away from you.

FastAPI, being a micro-framework, doesn't have a built-in auth system. It gives you the flexibility to choose your tools. We'll start by learning the classic approach using the passlib library, which is featured in many tutorials. However, in the spirit of learning modern, professional practices, I'll also introduce you to pwdlib, the library now recommended in the official FastAPI documentation, and explain why the change was made.

1. The Importance of Password Hashing

Before we write any code, it's essential to be clear on why we're doing this. Storing user passwords in plain text is a critical security vulnerability. If your database were ever compromised, an attacker would have direct access to every user's credentials.

The solution is hashing: a one-way cryptographic process that transforms a password into a fixed-length string of characters, called a hash. This process is irreversible, meaning you cannot get the original password back from its hash.

To understand this core concept, please watch the first minute of this video.

Hashing User Password: Part #57 Python API Corse

This video from Sanjeev Thiyagarajan provides a concise explanation of why we must hash passwords before storing them in a database.

Watch from the beginning until 01:06. The key takeaway is that hashing is a one-way street that protects user data even if the database is leaked.


2. Password Hashing with passlib

passlib is a comprehensive password hashing library for Python. It provides a convenient object, CryptContext, which manages the hashing algorithms and the verification process for us.

Installation

To get started, you need to install passlib along with a library for a specific hashing algorithm. We'll use bcrypt, which is a strong and widely trusted choice.

pip install "passlib[bcrypt]"

This command installs both passlib and the bcrypt library.

Configuration and Hashing

The process involves two main steps:

  1. Create a CryptContext instance and tell it which hashing algorithm to use.
  2. Use the hash() method of this context object to hash a new user's password.

The following video demonstrates this process clearly. It will guide you through creating the context and then integrating the hashing logic into a user creation endpoint.

Hashing User Password: Part #57 Python API Corse

Let's continue with the video from Sanjeev Thiyagarajan. It provides a step-by-step guide to setting up passlib and hashing a password.

Watch from 01:06 to 05:17. Pay close attention to: The installation of passlib and bcrypt. The creation of the pwd_context object from CryptContext. How pwd_context.hash(user.password) is called before the user is saved to the database.

As you saw in the video, the core logic looks like this:

from passlib.context import CryptContext




# 1. Create the CryptContext instance
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")




# 2. Define a function to hash a password
def hash_password(password: str):
    return pwd_context.hash(password)




# In your user creation logic:
# new_user.password = hash_password(form_data.password)
# db.add(new_user)
# db.commit()

Verification

Hashing is for storing passwords. Verification is for checking them at login. The CryptContext object also provides a verify() method for this. It takes two arguments: the plain-text password submitted by the user and the hashed password retrieved from your database.

This next video segment demonstrates the verification step.

FastAPI JWT Tutorial | How to add User Authentication

This clip from a tutorial by Eric Roby clearly shows how to use the .verify() method during a login attempt.

Watch from 12:18 to 12:58. Focus on the bcrypt_context.verify(password, user.hashed_password) call. It returns True if the password is correct and False otherwise.

So, a verification function would look like this:

def verify_password(plain_password: str, hashed_password: str):
    return pwd_context.verify(plain_password, hashed_password)




# In your login logic:
# user = get_user_from_db(username)
# if not user or not verify_password(submitted_password, user.hashed_password):
#     raise HTTPException(status_code=401, detail="Incorrect username or password")
# 
# # If verification succeeds, create a token

3. A Quick Note on Code Structure

Your goal is to build professional applications, so organizing your code is important. As shown in the first video, it's good practice to move your hashing logic into a separate utility file, for example, app/security.py or app/utils.py.

A more advanced, object-oriented approach is to use a class or mixin, which keeps the hashing and verification methods neatly bundled.

Structuring FastAPI application with multiple services using 3-tier ...

This article on structuring a FastAPI application shows a clean way to organize hashing logic within a HashingMixin class. This is a great pattern for reusability.

Read the code snippet under the 'Authentication' section. Specifically, look at how CryptContext is defined and how the HashingMixin class provides static methods for bcrypt and verify. This encapsulates the logic cleanly.


4. The Modern Way: pwdlib

While passlib is powerful and widely used, it is no longer actively maintained. This can lead to deprecation warnings and potential future incompatibilities. Because of this, the official FastAPI documentation has been updated to recommend a new library: pwdlib.

For your professional development, it's crucial to be aware of this and use the most current, supported tools.

passlib seems not being maintenanced anymore - GitHub

This GitHub discussion in the official FastAPI repository explains the situation. A contributor confirms the documentation has been updated.

Quickly scan this discussion. Read the initial post by 'dann2333' to understand the problem with passlib. Then, find the comment by 'YuriiMotov' (a FastAPI maintainer) confirming the switch to pwdlib in the official docs.

Using pwdlib is conceptually identical to passlib, but with a slightly simpler API.

Installation:

pip install "pwdlib[bcrypt]"

Usage:

from pwdlib import PasswordHash




# The context is created implicitly
password_hash = PasswordHash.recommended()




# Hashing
def hash_password(password: str) -> str:
    return password_hash.hash(password)




# Verification
def verify_password(plain_password: str, hashed_password: str) -> bool:
    return password_hash.verify(plain_password, hashed_password)

The PasswordHash.recommended() constructor automatically selects a strong, modern algorithm (like Argon2 if available, falling back to bcrypt), simplifying configuration. For any new project you start, pwdlib is the recommended choice.


5. Comparison: FastAPI vs. Django Hashing

Let's draw a direct comparison to solidify your understanding:

Feature Django FastAPI
Integration Built into django.contrib.auth. Requires installing a third-party library (pwdlib or passlib).
Configuration Done in settings.py via the PASSWORD_HASHERS list. Done in your Python code by creating a context object (CryptContext/PasswordHash).
Usage (Hashing) Abstracted. user.set_password(pwd) handles it internally. Explicit. You must call hash_password(pwd) yourself.
Usage (Verification) Abstracted. user.check_password(pwd) handles it internally. Explicit. You must call verify_password(pwd, hashed_pwd) yourself.
Responsibility The framework handles the low-level details. The developer is responsible for implementing the hashing and verification logic.
Default Algorithm PBKDF2 (historically), with easy configuration for Argon2, bcrypt, etc. Depends on the library (pwdlib recommends Argon2/bcrypt).

In essence, FastAPI gives you more control and responsibility, whereas Django provides a more "batteries-included," abstracted experience.


Conclusion

In this lesson, you've learned how to securely handle user passwords in a FastAPI application, a critical first step in building a robust authentication system.

Key Takeaways:

  • Never store plain-text passwords. Always use a strong, one-way hashing algorithm like bcrypt or Argon2.
  • The classic way to handle hashing in FastAPI is with passlib, using a CryptContext object to .hash() and .verify() passwords.
  • The modern, officially recommended library is pwdlib, which provides a simpler API (PasswordHash.recommended()) and is actively maintained.
  • Unlike Django's highly-abstracted auth system, FastAPI requires you to explicitly call hashing and verification functions in your application logic.

Preview of the Next Lesson:

Now that we can confidently verify a user's password, what's next? We need a way to keep them "logged in" for subsequent requests without sending their password every time. In our next lesson, we will create and decode JWT access tokens for stateless authentication. You'll learn how to generate a secure token upon successful login, which the user can then provide to access protected endpoints.