diff --git a/docs/en/docs/tutorial/security/oauth2-jwt.md b/docs/en/docs/tutorial/security/oauth2-jwt.md index 95baf871c..26894ab28 100644 --- a/docs/en/docs/tutorial/security/oauth2-jwt.md +++ b/docs/en/docs/tutorial/security/oauth2-jwt.md @@ -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 } diff --git a/docs_src/security/tutorial004_an_py310.py b/docs_src/security/tutorial004_an_py310.py index 368c743bf..685cb034e 100644 --- a/docs_src/security/tutorial004_an_py310.py +++ b/docs_src/security/tutorial004_an_py310.py @@ -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 diff --git a/docs_src/security/tutorial004_py310.py b/docs_src/security/tutorial004_py310.py index 8d0785b40..dc7f1c9e2 100644 --- a/docs_src/security/tutorial004_py310.py +++ b/docs_src/security/tutorial004_py310.py @@ -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 diff --git a/docs_src/security/tutorial005_an_py310.py b/docs_src/security/tutorial005_an_py310.py index fef0ab71c..9911723db 100644 --- a/docs_src/security/tutorial005_an_py310.py +++ b/docs_src/security/tutorial005_an_py310.py @@ -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 diff --git a/docs_src/security/tutorial005_py310.py b/docs_src/security/tutorial005_py310.py index 412fbf798..710cdac32 100644 --- a/docs_src/security/tutorial005_py310.py +++ b/docs_src/security/tutorial005_py310.py @@ -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