Skip to content

Commit efdafa4

Browse files
NeizvestnyjYuriiMotovtiangolo
authored
📝 Update tutorial/security/oauth2-jwt/ to use pwdlib with Argon2 instead of passlib (#13917)
Co-authored-by: Motov Yurii <[email protected]> Co-authored-by: Sebastián Ramírez <[email protected]>
1 parent 450a334 commit efdafa4

15 files changed

+75
-77
lines changed

docs/en/docs/how-to/conditional-openapi.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ If you want to secure your API, there are several better things you can do, for
1717
* Make sure you have well defined Pydantic models for your request bodies and responses.
1818
* Configure any required permissions and roles using dependencies.
1919
* Never store plaintext passwords, only password hashes.
20-
* Implement and use well-known cryptographic tools, like Passlib and JWT tokens, etc.
20+
* Implement and use well-known cryptographic tools, like pwdlib and JWT tokens, etc.
2121
* Add more granular permission controls with OAuth2 scopes where needed.
2222
* ...etc.
2323

docs/en/docs/tutorial/security/oauth2-jwt.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,20 @@ If your database is stolen, the thief won't have your users' plaintext passwords
6464

6565
So, the thief won't be able to try to use that password in another system (as many users use the same password everywhere, this would be dangerous).
6666

67-
## Install `passlib` { #install-passlib }
67+
## Install `pwdlib` { #install-pwdlib }
6868

69-
PassLib is a great Python package to handle password hashes.
69+
pwdlib is a great Python package to handle password hashes.
7070

7171
It supports many secure hashing algorithms and utilities to work with them.
7272

73-
The recommended algorithm is "Bcrypt".
73+
The recommended algorithm is "Argon2".
7474

75-
Make sure you create a [virtual environment](../../virtual-environments.md){.internal-link target=_blank}, activate it, and then install PassLib with Bcrypt:
75+
Make sure you create a [virtual environment](../../virtual-environments.md){.internal-link target=_blank}, activate it, and then install pwdlib with Argon2:
7676

7777
<div class="termy">
7878

7979
```console
80-
$ pip install "passlib[bcrypt]"
80+
$ pip install "pwdlib[argon2]"
8181

8282
---> 100%
8383
```
@@ -86,7 +86,7 @@ $ pip install "passlib[bcrypt]"
8686

8787
/// tip
8888

89-
With `passlib`, you could even configure it to be able to read passwords created by **Django**, a **Flask** security plug-in or many others.
89+
With `pwdlib`, you could even configure it to be able to read passwords created by **Django**, a **Flask** security plug-in or many others.
9090

9191
So, you would be able to, for example, share the same data from a Django application in a database with a FastAPI application. Or gradually migrate a Django application using the same database.
9292

@@ -96,15 +96,15 @@ And your users would be able to login from your Django app or from your **FastAP
9696

9797
## Hash and verify the passwords { #hash-and-verify-the-passwords }
9898

99-
Import the tools we need from `passlib`.
99+
Import the tools we need from `pwdlib`.
100100

101-
Create a PassLib "context". This is what will be used to hash and verify passwords.
101+
Create a PasswordHash instance with recommended settings - it will be used for hashing and verifying passwords.
102102

103103
/// tip
104104

105-
The PassLib context also has functionality to use different hashing algorithms, including deprecated old ones only to allow verifying them, etc.
105+
pwdlib also supports the bcrypt hashing algorithm but does not include legacy algorithms - for working with outdated hashes, it is recommended to use the passlib library.
106106

107-
For example, you could use it to read and verify passwords generated by another system (like Django) but hash any new passwords with a different algorithm like Bcrypt.
107+
For example, you could use it to read and verify passwords generated by another system (like Django) but hash any new passwords with a different algorithm like Argon2 or Bcrypt.
108108

109109
And be compatible with all of them at the same time.
110110

@@ -120,7 +120,7 @@ And another one to authenticate and return a user.
120120

121121
/// note
122122

123-
If you check the new (fake) database `fake_users_db`, you will see how the hashed password looks like now: `"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`.
123+
If you check the new (fake) database `fake_users_db`, you will see how the hashed password looks like now: `"$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc"`.
124124

125125
///
126126

@@ -264,7 +264,7 @@ Many packages that simplify it a lot have to make many compromises with the data
264264

265265
It gives you all the flexibility to choose the ones that fit your project the best.
266266

267-
And you can use directly many well maintained and widely used packages like `passlib` and `PyJWT`, because **FastAPI** doesn't require any complex mechanisms to integrate external packages.
267+
And you can use directly many well maintained and widely used packages like `pwdlib` and `PyJWT`, because **FastAPI** doesn't require any complex mechanisms to integrate external packages.
268268

269269
But it provides you the tools to simplify the process as much as possible without compromising flexibility, robustness, or security.
270270

docs_src/security/tutorial004.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from fastapi import Depends, FastAPI, HTTPException, status
66
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
77
from jwt.exceptions import InvalidTokenError
8-
from passlib.context import CryptContext
8+
from pwdlib import PasswordHash
99
from pydantic import BaseModel
1010

1111
# to get a string like this run:
@@ -20,7 +20,7 @@
2020
"username": "johndoe",
2121
"full_name": "John Doe",
2222
"email": "[email protected]",
23-
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
23+
"hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc",
2424
"disabled": False,
2525
}
2626
}
@@ -46,19 +46,19 @@ class UserInDB(User):
4646
hashed_password: str
4747

4848

49-
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
49+
password_hash = PasswordHash.recommended()
5050

5151
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
5252

5353
app = FastAPI()
5454

5555

5656
def verify_password(plain_password, hashed_password):
57-
return pwd_context.verify(plain_password, hashed_password)
57+
return password_hash.verify(plain_password, hashed_password)
5858

5959

6060
def get_password_hash(password):
61-
return pwd_context.hash(password)
61+
return password_hash.hash(password)
6262

6363

6464
def get_user(db, username: str):

docs_src/security/tutorial004_an.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from fastapi import Depends, FastAPI, HTTPException, status
66
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
77
from jwt.exceptions import InvalidTokenError
8-
from passlib.context import CryptContext
8+
from pwdlib import PasswordHash
99
from pydantic import BaseModel
1010
from typing_extensions import Annotated
1111

@@ -21,7 +21,7 @@
2121
"username": "johndoe",
2222
"full_name": "John Doe",
2323
"email": "[email protected]",
24-
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
24+
"hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc",
2525
"disabled": False,
2626
}
2727
}
@@ -47,19 +47,19 @@ class UserInDB(User):
4747
hashed_password: str
4848

4949

50-
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
50+
password_hash = PasswordHash.recommended()
5151

5252
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
5353

5454
app = FastAPI()
5555

5656

5757
def verify_password(plain_password, hashed_password):
58-
return pwd_context.verify(plain_password, hashed_password)
58+
return password_hash.verify(plain_password, hashed_password)
5959

6060

6161
def get_password_hash(password):
62-
return pwd_context.hash(password)
62+
return password_hash.hash(password)
6363

6464

6565
def get_user(db, username: str):

docs_src/security/tutorial004_an_py310.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from fastapi import Depends, FastAPI, HTTPException, status
66
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
77
from jwt.exceptions import InvalidTokenError
8-
from passlib.context import CryptContext
8+
from pwdlib import PasswordHash
99
from pydantic import BaseModel
1010

1111
# to get a string like this run:
@@ -20,7 +20,7 @@
2020
"username": "johndoe",
2121
"full_name": "John Doe",
2222
"email": "[email protected]",
23-
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
23+
"hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc",
2424
"disabled": False,
2525
}
2626
}
@@ -46,19 +46,19 @@ class UserInDB(User):
4646
hashed_password: str
4747

4848

49-
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
49+
password_hash = PasswordHash.recommended()
5050

5151
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
5252

5353
app = FastAPI()
5454

5555

5656
def verify_password(plain_password, hashed_password):
57-
return pwd_context.verify(plain_password, hashed_password)
57+
return password_hash.verify(plain_password, hashed_password)
5858

5959

6060
def get_password_hash(password):
61-
return pwd_context.hash(password)
61+
return password_hash.hash(password)
6262

6363

6464
def get_user(db, username: str):

docs_src/security/tutorial004_an_py39.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from fastapi import Depends, FastAPI, HTTPException, status
66
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
77
from jwt.exceptions import InvalidTokenError
8-
from passlib.context import CryptContext
8+
from pwdlib import PasswordHash
99
from pydantic import BaseModel
1010

1111
# to get a string like this run:
@@ -20,7 +20,7 @@
2020
"username": "johndoe",
2121
"full_name": "John Doe",
2222
"email": "[email protected]",
23-
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
23+
"hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc",
2424
"disabled": False,
2525
}
2626
}
@@ -46,19 +46,19 @@ class UserInDB(User):
4646
hashed_password: str
4747

4848

49-
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
49+
password_hash = PasswordHash.recommended()
5050

5151
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
5252

5353
app = FastAPI()
5454

5555

5656
def verify_password(plain_password, hashed_password):
57-
return pwd_context.verify(plain_password, hashed_password)
57+
return password_hash.verify(plain_password, hashed_password)
5858

5959

6060
def get_password_hash(password):
61-
return pwd_context.hash(password)
61+
return password_hash.hash(password)
6262

6363

6464
def get_user(db, username: str):

docs_src/security/tutorial004_py310.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from fastapi import Depends, FastAPI, HTTPException, status
55
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
66
from jwt.exceptions import InvalidTokenError
7-
from passlib.context import CryptContext
7+
from pwdlib import PasswordHash
88
from pydantic import BaseModel
99

1010
# to get a string like this run:
@@ -19,7 +19,7 @@
1919
"username": "johndoe",
2020
"full_name": "John Doe",
2121
"email": "[email protected]",
22-
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
22+
"hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc",
2323
"disabled": False,
2424
}
2525
}
@@ -45,19 +45,19 @@ class UserInDB(User):
4545
hashed_password: str
4646

4747

48-
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
48+
password_hash = PasswordHash.recommended()
4949

5050
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
5151

5252
app = FastAPI()
5353

5454

5555
def verify_password(plain_password, hashed_password):
56-
return pwd_context.verify(plain_password, hashed_password)
56+
return password_hash.verify(plain_password, hashed_password)
5757

5858

5959
def get_password_hash(password):
60-
return pwd_context.hash(password)
60+
return password_hash.hash(password)
6161

6262

6363
def get_user(db, username: str):

docs_src/security/tutorial005.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
SecurityScopes,
1010
)
1111
from jwt.exceptions import InvalidTokenError
12-
from passlib.context import CryptContext
12+
from pwdlib import PasswordHash
1313
from pydantic import BaseModel, ValidationError
1414

1515
# to get a string like this run:
@@ -24,14 +24,14 @@
2424
"username": "johndoe",
2525
"full_name": "John Doe",
2626
"email": "[email protected]",
27-
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
27+
"hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc",
2828
"disabled": False,
2929
},
3030
"alice": {
3131
"username": "alice",
3232
"full_name": "Alice Chains",
3333
"email": "[email protected]",
34-
"hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm",
34+
"hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$g2/AV1zwopqUntPKJavBFw$BwpRGDCyUHLvHICnwijyX8ROGoiUPwNKZ7915MeYfCE",
3535
"disabled": True,
3636
},
3737
}
@@ -58,7 +58,7 @@ class UserInDB(User):
5858
hashed_password: str
5959

6060

61-
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
61+
password_hash = PasswordHash.recommended()
6262

6363
oauth2_scheme = OAuth2PasswordBearer(
6464
tokenUrl="token",
@@ -69,11 +69,11 @@ class UserInDB(User):
6969

7070

7171
def verify_password(plain_password, hashed_password):
72-
return pwd_context.verify(plain_password, hashed_password)
72+
return password_hash.verify(plain_password, hashed_password)
7373

7474

7575
def get_password_hash(password):
76-
return pwd_context.hash(password)
76+
return password_hash.hash(password)
7777

7878

7979
def get_user(db, username: str):

docs_src/security/tutorial005_an.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
SecurityScopes,
1010
)
1111
from jwt.exceptions import InvalidTokenError
12-
from passlib.context import CryptContext
12+
from pwdlib import PasswordHash
1313
from pydantic import BaseModel, ValidationError
1414
from typing_extensions import Annotated
1515

@@ -25,14 +25,14 @@
2525
"username": "johndoe",
2626
"full_name": "John Doe",
2727
"email": "[email protected]",
28-
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
28+
"hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc",
2929
"disabled": False,
3030
},
3131
"alice": {
3232
"username": "alice",
3333
"full_name": "Alice Chains",
3434
"email": "[email protected]",
35-
"hashed_password": "$2b$12$gSvqqUPvlXP2tfVFaWK1Be7DlH.PKZbv5H8KnzzVgXXbVxpva.pFm",
35+
"hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$g2/AV1zwopqUntPKJavBFw$BwpRGDCyUHLvHICnwijyX8ROGoiUPwNKZ7915MeYfCE",
3636
"disabled": True,
3737
},
3838
}
@@ -59,7 +59,7 @@ class UserInDB(User):
5959
hashed_password: str
6060

6161

62-
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
62+
password_hash = PasswordHash.recommended()
6363

6464
oauth2_scheme = OAuth2PasswordBearer(
6565
tokenUrl="token",
@@ -70,11 +70,11 @@ class UserInDB(User):
7070

7171

7272
def verify_password(plain_password, hashed_password):
73-
return pwd_context.verify(plain_password, hashed_password)
73+
return password_hash.verify(plain_password, hashed_password)
7474

7575

7676
def get_password_hash(password):
77-
return pwd_context.hash(password)
77+
return password_hash.hash(password)
7878

7979

8080
def get_user(db, username: str):

0 commit comments

Comments
 (0)