Skip to content

Instantly share code, notes, and snippets.

@stuartaccent
Created March 15, 2023 12:37
Show Gist options
  • Save stuartaccent/1dcbc3cdbbe94c39a53edb10c976fc45 to your computer and use it in GitHub Desktop.
Save stuartaccent/1dcbc3cdbbe94c39a53edb10c976fc45 to your computer and use it in GitHub Desktop.
Basic auth in fastapi
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