Movatterモバイル変換


[0]ホーム

URL:


Skip to content
Join theFastAPI Cloud waiting list 🚀
Follow@fastapi onX (Twitter) to stay updated
FollowFastAPI onLinkedIn to stay updated
Subscribe to theFastAPI and friends newsletter 🎉
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor

HTTP Basic Auth

For the simplest cases, you can use HTTP Basic Auth.

In HTTP Basic Auth, the application expects a header that contains a username and a password.

If it doesn't receive it, it returns an HTTP 401 "Unauthorized" error.

And returns a headerWWW-Authenticate with a value ofBasic, and an optionalrealm parameter.

That tells the browser to show the integrated prompt for a username and password.

Then, when you type that username and password, the browser sends them in the header automatically.

Simple HTTP Basic Auth

  • ImportHTTPBasic andHTTPBasicCredentials.
  • Create a "security scheme" usingHTTPBasic.
  • Use thatsecurity with a dependency in yourpath operation.
  • It returns an object of typeHTTPBasicCredentials:
    • It contains theusername andpassword sent.
fromtypingimportAnnotatedfromfastapiimportDepends,FastAPIfromfastapi.securityimportHTTPBasic,HTTPBasicCredentialsapp=FastAPI()security=HTTPBasic()@app.get("/users/me")defread_current_user(credentials:Annotated[HTTPBasicCredentials,Depends(security)]):return{"username":credentials.username,"password":credentials.password}
🤓 Other versions and variants

Tip

Prefer to use theAnnotated version if possible.

fromfastapiimportDepends,FastAPIfromfastapi.securityimportHTTPBasic,HTTPBasicCredentialsapp=FastAPI()security=HTTPBasic()@app.get("/users/me")defread_current_user(credentials:HTTPBasicCredentials=Depends(security)):return{"username":credentials.username,"password":credentials.password}

When you try to open the URL for the first time (or click the "Execute" button in the docs) the browser will ask you for your username and password:

Check the username

Here's a more complete example.

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

For this, use the Python standard modulesecrets to check the username and password.

secrets.compare_digest() needs to takebytes or astr that only contains ASCII characters (the ones in English), this means it wouldn't work with characters likeá, as inSebastián.

To handle that, we first convert theusername andpassword tobytes encoding them with UTF-8.

Then we can usesecrets.compare_digest() to ensure thatcredentials.username is"stanleyjobson", and thatcredentials.password is"swordfish".

importsecretsfromtypingimportAnnotatedfromfastapiimportDepends,FastAPI,HTTPException,statusfromfastapi.securityimportHTTPBasic,HTTPBasicCredentialsapp=FastAPI()security=HTTPBasic()defget_current_username(credentials:Annotated[HTTPBasicCredentials,Depends(security)],):current_username_bytes=credentials.username.encode("utf8")correct_username_bytes=b"stanleyjobson"is_correct_username=secrets.compare_digest(current_username_bytes,correct_username_bytes)current_password_bytes=credentials.password.encode("utf8")correct_password_bytes=b"swordfish"is_correct_password=secrets.compare_digest(current_password_bytes,correct_password_bytes)ifnot(is_correct_usernameandis_correct_password):raiseHTTPException(status_code=status.HTTP_401_UNAUTHORIZED,detail="Incorrect username or password",headers={"WWW-Authenticate":"Basic"},)returncredentials.username@app.get("/users/me")defread_current_user(username:Annotated[str,Depends(get_current_username)]):return{"username":username}
🤓 Other versions and variants

Tip

Prefer to use theAnnotated version if possible.

importsecretsfromfastapiimportDepends,FastAPI,HTTPException,statusfromfastapi.securityimportHTTPBasic,HTTPBasicCredentialsapp=FastAPI()security=HTTPBasic()defget_current_username(credentials:HTTPBasicCredentials=Depends(security)):current_username_bytes=credentials.username.encode("utf8")correct_username_bytes=b"stanleyjobson"is_correct_username=secrets.compare_digest(current_username_bytes,correct_username_bytes)current_password_bytes=credentials.password.encode("utf8")correct_password_bytes=b"swordfish"is_correct_password=secrets.compare_digest(current_password_bytes,correct_password_bytes)ifnot(is_correct_usernameandis_correct_password):raiseHTTPException(status_code=status.HTTP_401_UNAUTHORIZED,detail="Incorrect username or password",headers={"WWW-Authenticate":"Basic"},)returncredentials.username@app.get("/users/me")defread_current_user(username:str=Depends(get_current_username)):return{"username":username}

This would be similar to:

ifnot(credentials.username=="stanleyjobson")ornot(credentials.password=="swordfish"):# Return some error...

But by using thesecrets.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 some attackers are trying to guess the username and password.

And they send a request with a usernamejohndoe and a passwordlove123.

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

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

But right at the moment Python compares the firstj injohndoe to the firsts instanleyjobson, it will returnFalse, 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 username or password".

But then the attackers try with usernamestanleyjobsox and passwordlove123.

And your application code does something like:

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

Python will have to compare the wholestanleyjobso in bothstanleyjobsox andstanleyjobson before realizing that both strings are not the same. So it will take some extra microseconds to reply back "Incorrect username or password".

The time to answer helps the attackers

At that point, by noticing that the server took some microseconds longer to send the "Incorrect username or password" response, the attackers will know that they gotsomething right, some of the initial letters were right.

And then they can try again knowing that it's probably something more similar tostanleyjobsox than tojohndoe.

A "professional" attack

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

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

Fix it withsecrets.compare_digest()

But in our code we are actually usingsecrets.compare_digest().

In short, it will take the same time to comparestanleyjobsox tostanleyjobson than it takes to comparejohndoe tostanleyjobson. And the same for the password.

That way, usingsecrets.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 anHTTPException with a status code 401 (the same returned when no credentials are provided) and add the headerWWW-Authenticate to make the browser show the login prompt again:

importsecretsfromtypingimportAnnotatedfromfastapiimportDepends,FastAPI,HTTPException,statusfromfastapi.securityimportHTTPBasic,HTTPBasicCredentialsapp=FastAPI()security=HTTPBasic()defget_current_username(credentials:Annotated[HTTPBasicCredentials,Depends(security)],):current_username_bytes=credentials.username.encode("utf8")correct_username_bytes=b"stanleyjobson"is_correct_username=secrets.compare_digest(current_username_bytes,correct_username_bytes)current_password_bytes=credentials.password.encode("utf8")correct_password_bytes=b"swordfish"is_correct_password=secrets.compare_digest(current_password_bytes,correct_password_bytes)ifnot(is_correct_usernameandis_correct_password):raiseHTTPException(status_code=status.HTTP_401_UNAUTHORIZED,detail="Incorrect username or password",headers={"WWW-Authenticate":"Basic"},)returncredentials.username@app.get("/users/me")defread_current_user(username:Annotated[str,Depends(get_current_username)]):return{"username":username}
🤓 Other versions and variants

Tip

Prefer to use theAnnotated version if possible.

importsecretsfromfastapiimportDepends,FastAPI,HTTPException,statusfromfastapi.securityimportHTTPBasic,HTTPBasicCredentialsapp=FastAPI()security=HTTPBasic()defget_current_username(credentials:HTTPBasicCredentials=Depends(security)):current_username_bytes=credentials.username.encode("utf8")correct_username_bytes=b"stanleyjobson"is_correct_username=secrets.compare_digest(current_username_bytes,correct_username_bytes)current_password_bytes=credentials.password.encode("utf8")correct_password_bytes=b"swordfish"is_correct_password=secrets.compare_digest(current_password_bytes,correct_password_bytes)ifnot(is_correct_usernameandis_correct_password):raiseHTTPException(status_code=status.HTTP_401_UNAUTHORIZED,detail="Incorrect username or password",headers={"WWW-Authenticate":"Basic"},)returncredentials.username@app.get("/users/me")defread_current_user(username:str=Depends(get_current_username)):return{"username":username}

[8]ページ先頭

©2009-2026 Movatter.jp