Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/src/security/tutorial007.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import secrets

from fastapi import Depends, FastAPI, HTTPException
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from starlette.status import HTTP_401_UNAUTHORIZED
Expand All @@ -8,7 +10,9 @@


def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
if credentials.username != "foo" or credentials.password != "password":
correct_username = secrets.compare_digest(credentials.username, "stanleyjobson")
correct_password = secrets.compare_digest(credentials.password, "swordfish")
if not correct_username and correct_password:

@StephenBrown2 StephenBrown2 Jan 20, 2020

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs parentheses around it, or change the comparison to or not:

Suggested change
if not correct_username and correct_password:
if not (correct_username and correct_password):

Otherwise, if the username is bad but the password is good, the check will pass, as well as if the username is good, the check will pass regardless of the password.
Example:
https://repl.it/repls/WigglyLowestBackups

EDIT: Whoops, looks like this was fixed in #865. 👍 Truth tables are hard. :-)

raise HTTPException(
status_code=HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
Expand Down
70 changes: 68 additions & 2 deletions docs/tutorial/security/http-basic-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,74 @@ Here's a more complete example.

Use a dependency to check if the username and password are correct.

If the credentials are incorrect, return an `HTTPException` with a status code 401 (the same returned when no credentials are provided) and add the header `WWW-Authenticate` to make the browser show the login prompt again:
For this, use the Python standard module <a href="https://docs.python.org/3/library/secrets.html" target="_blank">`secrets`</a> to check the username and password:

```Python hl_lines="10 11 12 13 14 15 16 17 21"
```Python hl_lines="1 13 14 15"
{!./src/security/tutorial007.py!}
```

This will ensure that `credentials.username` is `"stanleyjobson"`, and that `credentials.password` is `"swordfish"`. This would be similar to:

```Python
if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"):
# Return some error
...
```

But by using the `secrets.compare_digest()` it will be secure against a type of attacks called "timing attacks".

### Timing Attacks

But what's a "timing attack"?

Let's imagine an attacker is trying to guess the username and password.

And that attacker sends a request with a username `johndoe` and a password `love123`.

Then the Python code in your application would be equivalent to something like:

```Python
if "johndoe" == "stanleyjobson" and "love123" == "swordfish":
...
```

But right at the moment Python compares the first `j` in `johndoe` to the first `s` in `stanleyjobson`, it will return `False`, because it already knows that those two strings are not the same, thinking that "there's no need to waste more computation comparing the rest of the letters". And your application will say "incorrect user or password".

But then the attacker tries with username `stanleyjobsox` and password `love123`.

And your application code does something like:

```Python
if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
...
```

Python will have to compare the whole `stanleyjobso` in both `stanleyjobsox` and `stanleyjobson` before realizing that both strings are not the same. So it will take some extra microseconds to reply back "incorrect user or password".

#### The time to answer helps the attacker

At that point, by noticing that the server took some microseconds longer to send the "incorrect user or password" response, the attacker will know that she/he got _something_ right, some of the initial letters were right.

And then she/he can try again knowing that it's probably something more similar to `stanleyjobsox` than to `johndoe`.

#### A "professional" attack

Of course, the attacker would not try all this by hand, she/he would write a program to do it, possibly with thousands or millions of tests per second. And would get just one extra correct letter at a time.

But doing that, in some minutes or hours the attacker would have guessed the correct username and password, with the "help" of our application, just using the time taken to answer.

#### Fix it with `secrets.compare_digest()`

But in our code we are actually using `secrets.compare_digest()`.

In short, it will take the same time to compare `stanleyjobsox` to `stanleyjobson` than it takes to compare `johndoe` to `stanleyjobson`. And the same for the password.

That way, using `secrets.compare_digest()` in your application code, it will be safe against this whole range of security attacks.

### Return the error

After detecting that the credentials are incorrect, return an `HTTPException` with a status code 401 (the same returned when no credentials are provided) and add the header `WWW-Authenticate` to make the browser show the login prompt again:

```Python hl_lines="16 17 18 19 20"
{!./src/security/tutorial007.py!}
```