I'm encountering an intermittent issue with my FastAPI application where the Authorization headers switch from Bearer token to Basic auth without any changes to the request being made. This issue resolves itself without any clear intervention, making it particularly perplexing. The application is containerised using Docker, and I'm testing the endpoints using the auto-docs generated by FastAPI.
Here is the code I am using to verify the user access token:
from fastapi import HTTPException, Depends, status, Request
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from jose import jwt, JWTError, ExpiredSignatureError
from starlette.datastructures import Secret
from common.settings import ACCESS_SECRET_KEY, HASHING_ALGORITHM
# Initialization of HTTPBearer for security
security = HTTPBearer()
# Exception for invalid user credentials
INVALID_USER_HTTP_EXCEPTION = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials.",
headers={"WWW-Authenticate": "Bearer"},
)
def decode_jwt_token(token: str, secret_key: Secret) -> dict:
"""Decodes a JWT token using the provided secret key.
This function validates a JWT token's structure and ensures it hasn't expired.
It distinguishes between tracking tokens and access/refresh tokens, applying
specific validations for each.
Args:
token: The JWT token to be decoded.
secret_key: The secret key used for decoding the token.
Returns:
The decoded token payload as a dictionary.
Raises:
HTTPException: For invalid tokens, expired tokens, or tokens with missing fields.
"""
try:
payload = jwt.decode(token, key=str(secret_key), algorithms=[HASHING_ALGORITHM])
token_type = payload.get('token_type')
if token_type not in ('tracking', 'access', 'refresh'):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid token type.")
if token_type == 'tracking' and (not payload.get('contact_id') or not payload.get('sequence_id')):
raise INVALID_USER_HTTP_EXCEPTION
if token_type in ('access', 'refresh') and not payload.get("sub"):
raise INVALID_USER_HTTP_EXCEPTION
return payload
except ExpiredSignatureError:
raise HTTPException(status_code=440, detail="Token has expired. Log-in again.", headers={"WWW-Authenticate": "Bearer"})
except JWTError as e:
_logger.info(f"JWTError: {e}")
raise INVALID_USER_HTTP_EXCEPTION
def verify_access_token(authorization: HTTPAuthorizationCredentials = Depends(security)) -> str:
"""Verifies an access token and retrieves the user's email.
Decodes the access token from the authorization header to ensure it is valid
and extracts the user's email address from it.
Args:
authorization: The authorization credentials containing the JWT token.
Returns:
The user's email address extracted from the token.
Raises:
HTTPException: If the token is invalid or if the email is missing.
"""
token = authorization.credentials
payload = decode_jwt_token(token=token, secret_key=ACCESS_SECRET_KEY)
email = payload.get("sub")
if email is None:
raise INVALID_USER_HTTP_EXCEPTION
return email
Running the above, I kept receiving a 403 response with the message "Invalid authentication credentials". This is the response that FastAPI automatically returns when the authorization scheme of the request Header is not equal to "bearer".
When sending a GET request to my FastAPI endpoint with a Bearer token in the Authorization header, the application sometimes logs the Authorization header as Basic auth instead. This issue appears randomly and resolves itself after some time. Below are the details of the curl command and the logs from two different attempts:
curl -X 'GET' \
'http://localhost/users/' \
-H 'accept: application/json' \
-H 'Authorization: Bearer <token>'
First Attempt Log (Issue Present):
2024-03-26 10:02:56
Headers: Headers(
{
"host": "localhost",
"authorization": "Basic YWRtaW...",
"sec-fetch-site": "same-origin",
"accept-language": "en-GB,en;q=0.9",
"accept-encoding": "gzip, deflate",
"sec-fetch-mode": "cors",
"accept": "application/json",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Safari/605.1.15",
"connection": "keep-alive",
"referer": "http://localhost/docs",
"cookie": "session=eyJzdGF0ZSI6I...",
"sec-fetch-dest": "empty",
}
)
Second Attempt Log (Issue Resolved, 17 minutes later):
2024-03-26 10:19:12
Headers: Headers(
{
"host": "localhost",
"authorization": "Bearer <token>",
"sec-fetch-site": "same-origin",
"accept-language": "en-GB,en;q=0.9",
"accept-encoding": "gzip, deflate",
"sec-fetch-mode": "cors",
"accept": "application/json",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Safari/605.1.15",
"connection": "keep-alive",
"referer": "http://localhost/docs",
"cookie": "session=eyJzdGF0ZS...",
"sec-fetch-dest": "empty",
}
)
Attempts to Diagnose/Resolve the Issue:
- Ensured no caching proxies or layers are interfering with the requests.
- Checked Docker container and network configurations for potential misconfigurations.
- Reviewed FastAPI and related dependencies for updates or known issues.
Are there known issues with FastAPI's HTTPBearer class that might cause this intermittent recognition of Bearer vs. Basic auth headers? How might I go about fixing this issue?