From 0125ea4f83b29ae51386eb33cd7409aa98be93f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 28 Dec 2018 16:03:54 +0400 Subject: [PATCH] :memo: Update tutorials --- docs/src/security/tutorial003.py | 9 +++++- docs/tutorial/extra-models.md | 37 ++++++++++++++++++++- docs/tutorial/security/oauth2-jwt.md | 2 +- docs/tutorial/security/simple-oauth2.md | 43 ++++++++++++++++++++++--- 4 files changed, 83 insertions(+), 8 deletions(-) diff --git a/docs/src/security/tutorial003.py b/docs/src/security/tutorial003.py index e10384c63..d18330e9e 100644 --- a/docs/src/security/tutorial003.py +++ b/docs/src/security/tutorial003.py @@ -10,6 +10,13 @@ fake_users_db = { "email": "johndoe@example.com", "hashed_password": "fakehashedsecret", "disabled": False, + }, + "alice": { + "username": "alice", + "full_name": "Alice Wonderson", + "email": "alice@example.com", + "hashed_password": "fakehashedsecret2", + "disabled": True, } } @@ -68,7 +75,7 @@ async def login(form_data: OAuth2PasswordRequestForm = Depends()): if not user_dict: raise HTTPException(status_code=400, detail="Incorrect username or password") user = UserInDB(**user_dict) - hashed_password = fake_hash_password(data.password) + hashed_password = fake_hash_password(form_data.password) if not hashed_password == user.hashed_password: raise HTTPException(status_code=400, detail="Incorrect username or password") diff --git a/docs/tutorial/extra-models.md b/docs/tutorial/extra-models.md index 1cf727df8..ad35ffeef 100644 --- a/docs/tutorial/extra-models.md +++ b/docs/tutorial/extra-models.md @@ -7,7 +7,9 @@ This is especially the case for user models, because: * The **database model** would probably need to have a hashed password. !!! danger - Never store user's plaintext passwords. Always store a secure hash that you can then verify. + Never store user's plaintext passwords. Always store a "secure hash" that you can then verify. + + If you don't know, you will learn what a "password hash" is in the security chapters. ## Multiple models @@ -17,6 +19,39 @@ Here's a general idea of how the models could look like with their password fiel {!./src/extra_models/tutorial001.py!} ``` +#### About `**user_dict` + +`UserInDB(**user_dict)` means: + +Pass the keys and values of the `user_dict` directly as key-value arguments, equivalent to: + +```Python +UserInDB( + username = user_dict["username"], + password = user_dict["password"], + email = user_dict["email"], + full_name = user_dict["full_name"], +) +``` + +And then adding the extra `hashed_password=hashed_password`, like in: + +```Python +UserInDB(**user_in.dict(), hashed_password=hashed_password) +``` + +...ends up being like: + +```Python +UserInDB( + username = user_dict["username"], + password = user_dict["password"], + email = user_dict["email"], + full_name = user_dict["full_name"], + hashed_password = hashed_password, +) +``` + !!! warning The supporting additional functions are just to demo a possible flow of the data, but they of course are not providing any real security. diff --git a/docs/tutorial/security/oauth2-jwt.md b/docs/tutorial/security/oauth2-jwt.md index 8544514a4..17756468b 100644 --- a/docs/tutorial/security/oauth2-jwt.md +++ b/docs/tutorial/security/oauth2-jwt.md @@ -52,7 +52,7 @@ The recommended algorithm is "Bcrypt". So, install PassLib with Bcrypt: -```Python +```bash pip install passlib[bcrypt] ``` diff --git a/docs/tutorial/security/simple-oauth2.md b/docs/tutorial/security/simple-oauth2.md index 93cf5e995..3d5a1a546 100644 --- a/docs/tutorial/security/simple-oauth2.md +++ b/docs/tutorial/security/simple-oauth2.md @@ -48,7 +48,7 @@ Now let's use the utilities provided by **FastAPI** to handle this. First, import `OAuth2PasswordRequestForm`, and use it as a dependency with `Depends` for the path `/token`: -```Python hl_lines="2 66" +```Python hl_lines="2 73" {!./src/security/tutorial003.py!} ``` @@ -80,7 +80,7 @@ If there is no such user, we return an error saying "incorrect username or passw For the error, we use the exception `HTTPException` provided by Starlette directly: -```Python hl_lines="4 67 68 69" +```Python hl_lines="4 74 75 76" {!./src/security/tutorial003.py!} ``` @@ -94,7 +94,21 @@ You should never save plaintext passwords, so, we'll use the (fake) password has If the passwords don't match, we return the same error. -```Python hl_lines="70 71 72 73" +#### Password hashing + +"Hashing" means: converting some content (a password in this case) into a sequence of bytes (just a string) that look like gibberish. + +Whenever you pass exactly the same content (exactly the same password) you get exactly the same gibberish. + +But you cannot convert from the gibberish back to the password. + +##### What for? + +If your database is stolen, the thief won't have your users' plaintext passwords, only the hashes. + +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). + +```Python hl_lines="77 78 79 80" {!./src/security/tutorial003.py!} ``` @@ -129,7 +143,7 @@ For this simple example, we are going to just be completely insecure and return But for now, let's focus on the specific details we need. -```Python hl_lines="75" +```Python hl_lines="82" {!./src/security/tutorial003.py!} ``` @@ -145,7 +159,7 @@ Both of these dependencies will just return an HTTP error if the user doesn't ex So, in our endpoint, we will only get a user if the user exists, was correctly authenticated, and is active: -```Python hl_lines="50 51 52 53 54 55 56 59 60 61 62 79" +```Python hl_lines="57 58 59 60 61 62 63 66 67 68 69 86" {!./src/security/tutorial003.py!} ``` @@ -160,6 +174,7 @@ Click the "Authorize" button. Use the credentials: User: `johndoe` + Password: `secret` @@ -194,6 +209,24 @@ If you click the lock icon and logout, and then try the same operation again, yo } ``` +### Inactive user + +Now try with an inactive user, authenticate with: + +User: `alice` + +Password: `secret2` + +And try to use the operation `GET` with the path `/users/me`. + +You will get an "inactive user" error, like: + +```JSON +{ + "detail": "Inactive user" +} +``` + ## Recap You now have the tools to implement a complete security system based on `username` and `password` for your API.