Created
March 15, 2023 12:37
-
-
Save stuartaccent/1dcbc3cdbbe94c39a53edb10c976fc45 to your computer and use it in GitHub Desktop.
Basic auth in fastapi
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import secrets | |
from fastapi import Depends, HTTPException | |
from fastapi.security import HTTPBasic, HTTPBasicCredentials | |
from starlette import status | |
from app.config import settings | |
security = HTTPBasic() | |
# required additions to settings | |
class Settings(BaseSettings): | |
... | |
# basic auth | |
# to get a string like this run: openssl rand -hex 32 | |
basic_username: SecretBytes = b"651f25ef9d46b9f837324906fdef3e6ed98d99e3f3f0024a7d31660aff946e2c" | |
basic_password: SecretBytes = b"0c682bee542c346a505230e9d96d1e57e3b89ef05d1b43fc99a26657d94cacf9" | |
def get_basic_auth_username( | |
credentials: HTTPBasicCredentials = Depends(security), | |
): | |
""" | |
The below is an example of basic auth. | |
USAGE: | |
@router.get("/") | |
async def root(username: str = Depends(get_basic_auth_username)): | |
return {"status": "hunky dory"} | |
IMPORTANT NOTES: | |
the usage of secrets.compare_digest ensures that it takes the same | |
length of time to check the credentials regardless of their length. | |
for example if the check was: | |
if username == "username" and password == "password" | |
the auth is vulnerable to attack. it will take a tiny amount longer | |
to check `"user" == "username"` than it would `"usefoo" == "username"` | |
as a string equality check stops at the point it finds the first incorrect | |
letter. A program handling 1000's of requests can work out the creds based | |
on the time it takes to validate it. The longer it takes the more | |
of the creds they know. | |
Using bytes and `secrets.compare_digest` it will ensure it takes the same | |
time regardless of value used. | |
""" | |
# check username | |
current_username_bytes = credentials.username.encode("utf8") | |
correct_username_bytes = settings.basic_username.get_secret_value() | |
is_correct_username = secrets.compare_digest( | |
current_username_bytes, | |
correct_username_bytes, | |
) | |
# check password | |
current_password_bytes = credentials.password.encode("utf8") | |
correct_password_bytes = settings.basic_password.get_secret_value() | |
is_correct_password = secrets.compare_digest( | |
current_password_bytes, | |
correct_password_bytes, | |
) | |
# if not correct credentials raise 401 | |
if not (is_correct_username and is_correct_password): | |
raise HTTPException( | |
status_code=status.HTTP_401_UNAUTHORIZED, | |
detail="Incorrect email or password", | |
headers={"WWW-Authenticate": "Basic"}, | |
) | |
return credentials.username |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment