mirror of
https://github.com/fastapi-users/fastapi-users.git
synced 2026-03-13 07:49:55 +08:00
Replace passlib in favor of pwdlib
This commit is contained in:
@@ -1,21 +1,24 @@
|
||||
# Password hash
|
||||
|
||||
By default, FastAPI Users will use the [BCrypt algorithm](https://en.wikipedia.org/wiki/Bcrypt) to **hash and salt** passwords before storing them in the database.
|
||||
By default, FastAPI Users will use the [Argon2 algorithm](https://en.wikipedia.org/wiki/Argon2) to **hash and salt** passwords before storing them in the database, with backwards-compatibility with [Bcrypt](https://en.wikipedia.org/wiki/Bcrypt).
|
||||
|
||||
The implementation is provided by [Passlib](https://passlib.readthedocs.io/en/stable/index.html), a battle-tested Python library for password hashing.
|
||||
The implementation is provided by [pwdlib](https://github.com/frankie567/pwdlib), a modern password hashing wrapper.
|
||||
|
||||
## Customize `CryptContext`
|
||||
## Customize `PasswordHash`
|
||||
|
||||
If you need to support other hashing algorithms, you can customize the [`CryptContext` object of Passlib](https://passlib.readthedocs.io/en/stable/lib/passlib.context.html#the-cryptcontext-class).
|
||||
If you need to tune the algorithms used or their settings, you can customize the [`PasswordHash` object of pwdlib](https://frankie567.github.io/pwdlib/reference/pwdlib/#pwdlib.PasswordHash).
|
||||
|
||||
For this, you'll need to instantiate the `PasswordHelper` class and pass it your `CryptContext`. The example below shows you how you can create a `CryptContext` to add support for the Argon2 algorithm while deprecating BCrypt.
|
||||
For this, you'll need to instantiate the `PasswordHelper` class and pass it your `PasswordHash`. The example below shows you how you can create a `PasswordHash` to only support the Argon2 algorithm.
|
||||
|
||||
```py
|
||||
from fastapi_users.password import PasswordHelper
|
||||
from passlib.context import CryptContext
|
||||
from pwdlib import PasswordHash, exceptions
|
||||
from pwdlib.hashers.argon2 import Argon2Hasher
|
||||
|
||||
context = CryptContext(schemes=["argon2", "bcrypt"], deprecated="auto")
|
||||
password_helper = PasswordHelper(context)
|
||||
password_hash = PasswordHash((
|
||||
Argon2Hasher(),
|
||||
))
|
||||
password_helper = PasswordHelper(password_hash)
|
||||
```
|
||||
|
||||
Finally, pass the `password_helper` variable while instantiating your `UserManager`:
|
||||
@@ -32,12 +35,9 @@ async def get_user_manager(user_db=Depends(get_user_db)):
|
||||
|
||||
If it is, we take the opportunity of having the password in plain-text at hand (since the user just logged in!) to hash it with a better algorithm and update it in database.
|
||||
|
||||
!!! warning "Dependencies for alternative algorithms are not included by default"
|
||||
FastAPI Users won't install required dependencies to make other algorithms like Argon2 work. It's up to you to install them.
|
||||
|
||||
## Full customization
|
||||
|
||||
If you don't wish to use Passlib at all – **which we don't recommend unless you're absolutely sure of what you're doing** — you can implement your own `PasswordHelper` class as long as it implements the `PasswordHelperProtocol` and its methods.
|
||||
If you don't wish to use `pwdlib` at all – **which we don't recommend unless you're absolutely sure of what you're doing** — you can implement your own `PasswordHelper` class as long as it implements the `PasswordHelperProtocol` and its methods.
|
||||
|
||||
```py
|
||||
from typing import Tuple
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
from typing import Optional, Protocol, Tuple
|
||||
import secrets
|
||||
from typing import Optional, Protocol, Tuple, Union
|
||||
|
||||
from passlib import pwd
|
||||
from passlib.context import CryptContext
|
||||
from pwdlib import PasswordHash
|
||||
from pwdlib.hashers.argon2 import Argon2Hasher
|
||||
from pwdlib.hashers.bcrypt import BcryptHasher
|
||||
|
||||
|
||||
class PasswordHelperProtocol(Protocol):
|
||||
def verify_and_update(
|
||||
self, plain_password: str, hashed_password: str
|
||||
) -> Tuple[bool, str]: ... # pragma: no cover
|
||||
) -> Tuple[bool, Union[str, None]]: ... # pragma: no cover
|
||||
|
||||
def hash(self, password: str) -> str: ... # pragma: no cover
|
||||
|
||||
@@ -15,19 +17,24 @@ class PasswordHelperProtocol(Protocol):
|
||||
|
||||
|
||||
class PasswordHelper(PasswordHelperProtocol):
|
||||
def __init__(self, context: Optional[CryptContext] = None) -> None:
|
||||
if context is None:
|
||||
self.context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
def __init__(self, password_hash: Optional[PasswordHash] = None) -> None:
|
||||
if password_hash is None:
|
||||
self.password_hash = PasswordHash(
|
||||
(
|
||||
Argon2Hasher(),
|
||||
BcryptHasher(),
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.context = context # pragma: no cover
|
||||
self.password_hash = password_hash # pragma: no cover
|
||||
|
||||
def verify_and_update(
|
||||
self, plain_password: str, hashed_password: str
|
||||
) -> Tuple[bool, str]:
|
||||
return self.context.verify_and_update(plain_password, hashed_password)
|
||||
) -> Tuple[bool, Union[str, None]]:
|
||||
return self.password_hash.verify_and_update(plain_password, hashed_password)
|
||||
|
||||
def hash(self, password: str) -> str:
|
||||
return self.context.hash(password)
|
||||
return self.password_hash.hash(password)
|
||||
|
||||
def generate(self) -> str:
|
||||
return pwd.genword()
|
||||
return secrets.token_urlsafe()
|
||||
|
||||
@@ -141,8 +141,7 @@ classifiers = [
|
||||
requires-python = ">=3.8"
|
||||
dependencies = [
|
||||
"fastapi >=0.65.2",
|
||||
"passlib[bcrypt] ==1.7.4; python_version < '3.12'",
|
||||
"bcrypt ==4.1.2; python_version >= '3.12'",
|
||||
"pwdlib[argon2,bcrypt] ==0.2.0",
|
||||
"email-validator >=1.1.0,<2.2",
|
||||
"pyjwt[crypto] ==2.8.0",
|
||||
"python-multipart ==0.0.7",
|
||||
|
||||
Reference in New Issue
Block a user