📝 Update docs for JWT to prevent timing attacks (#14908)

This commit is contained in:
Sebastián Ramírez 2026-02-12 10:10:35 -08:00 committed by GitHub
parent a2e51363c7
commit d11f820ac3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 20 additions and 4 deletions

View File

@ -116,7 +116,11 @@ And another utility to verify if a received password matches the hash stored.
And another one to authenticate and return a user.
{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *}
{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,51,58:59,62:63,72:79] *}
When `authenticate_user` is called with a username that doesn't exist in the database, we still run `verify_password` against a dummy hash.
This ensures the endpoint takes roughly the same amount of time to respond whether the username is valid or not, preventing **timing attacks** that could be used to enumerate existing usernames.
/// note
@ -152,7 +156,7 @@ Define a Pydantic Model that will be used in the token endpoint for the response
Create a utility function to generate a new access token.
{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *}
{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,82:90] *}
## Update the dependencies { #update-the-dependencies }
@ -162,7 +166,7 @@ Decode the received token, verify it, and return the current user.
If the token is invalid, return an HTTP error right away.
{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *}
{* ../../docs_src/security/tutorial004_an_py310.py hl[93:110] *}
## Update the `/token` *path operation* { #update-the-token-path-operation }
@ -170,7 +174,7 @@ Create a `timedelta` with the expiration time of the token.
Create a real JWT access token and return it.
{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *}
{* ../../docs_src/security/tutorial004_an_py310.py hl[121:136] *}
### Technical details about the JWT "subject" `sub` { #technical-details-about-the-jwt-subject-sub }

View File

@ -48,6 +48,8 @@ class UserInDB(User):
password_hash = PasswordHash.recommended()
DUMMY_HASH = password_hash.hash("dummypassword")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
app = FastAPI()
@ -70,6 +72,7 @@ def get_user(db, username: str):
def authenticate_user(fake_db, username: str, password: str):
user = get_user(fake_db, username)
if not user:
verify_password(password, DUMMY_HASH)
return False
if not verify_password(password, user.hashed_password):
return False

View File

@ -47,6 +47,8 @@ class UserInDB(User):
password_hash = PasswordHash.recommended()
DUMMY_HASH = password_hash.hash("dummypassword")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
app = FastAPI()
@ -69,6 +71,7 @@ def get_user(db, username: str):
def authenticate_user(fake_db, username: str, password: str):
user = get_user(fake_db, username)
if not user:
verify_password(password, DUMMY_HASH)
return False
if not verify_password(password, user.hashed_password):
return False

View File

@ -60,6 +60,8 @@ class UserInDB(User):
password_hash = PasswordHash.recommended()
DUMMY_HASH = password_hash.hash("dummypassword")
oauth2_scheme = OAuth2PasswordBearer(
tokenUrl="token",
scopes={"me": "Read information about the current user.", "items": "Read items."},
@ -85,6 +87,7 @@ def get_user(db, username: str):
def authenticate_user(fake_db, username: str, password: str):
user = get_user(fake_db, username)
if not user:
verify_password(password, DUMMY_HASH)
return False
if not verify_password(password, user.hashed_password):
return False

View File

@ -59,6 +59,8 @@ class UserInDB(User):
password_hash = PasswordHash.recommended()
DUMMY_HASH = password_hash.hash("dummypassword")
oauth2_scheme = OAuth2PasswordBearer(
tokenUrl="token",
scopes={"me": "Read information about the current user.", "items": "Read items."},
@ -84,6 +86,7 @@ def get_user(db, username: str):
def authenticate_user(fake_db, username: str, password: str):
user = get_user(fake_db, username)
if not user:
verify_password(password, DUMMY_HASH)
return False
if not verify_password(password, user.hashed_password):
return False