57.3 Authentication: Basic, Digest, Bearer, OAuth
Authentication is a cornerstone of interacting with modern web APIs and services. It is the process of proving your identity to a server, which then grants you permission to access specific resources. The httpx library provides robust, built-in support for the most common authentication schemes, streamlining the process of making authenticated requests. Understanding the nuances of each method is crucial for building secure and effective API clients.
Basic Authentication
Basic Authentication is one of the simplest and most widely supported methods. It involves sending a username and password with each request. The credentials are concatenated with a colon (username:password), base64-encoded, and placed in the Authorization header.
Why it works this way: The encoding is not encryption; it is merely a way to represent binary data as ASCII text. This is a critical point: Basic Auth transmits credentials in a trivially reversible format. Therefore, it must only be used over HTTPS to prevent eavesdropping and man-in-the-middle attacks.
import httpx
import base64
# Method 1: Using the `auth` parameter (recommended)
username = "my_user"
password = "my_pass123"
with httpx.Client(auth=(username, password)) as client:
response = client.get("https://httpbin.org/basic-auth/my_user/my_pass123")
print(response.status_code) # 200
# Method 2: Manually constructing the header (for understanding)
credentials = base64.b64encode(f"{username}:{password}".encode()).decode()
headers = {"Authorization": f"Basic {credentials}"}
response = httpx.get("https://httpbin.org/basic-auth/my_user/my_pass123", headers=headers)
print(response.status_code) # 200
Common Pitfall: The most significant risk is forgetting to use HTTPS, exposing the credentials. Additionally, many APIs have rate limits on failed authentication attempts, and repeatedly sending incorrect credentials via Basic Auth can quickly lead to a temporary ban.
Digest Authentication
Digest Authentication was designed as a more secure alternative to Basic Auth. Instead of sending the password itself, the client proves it knows the password by responding to a challenge from the server with a calculated hash (using the MD5 algorithm by default).
Why it works this way: The server sends a nonce (a number used once) and other information. The client combines this nonce with the username, password, and HTTP method to create a hash. This means the password is never sent over the wire, protecting against passive eavesdropping. However, it is still vulnerable to more advanced attacks and is considered outdated for protecting highly sensitive information.
import httpx
from httpx import DigestAuth
username = "my_user"
password = "my_pass123"
# httpx handles the entire challenge-response flow automatically.
with httpx.Client() as client:
response = client.get(
"https://httpbin.org/digest-auth/auth/my_user/my_pass123",
auth=DigestAuth(username, password)
)
print(response.status_code) # 200
Best Practice: While more secure than Basic over HTTP, Digest should still be used over HTTPS. Prefer more modern token-based methods like Bearer tokens for new applications.
Bearer Token Authentication
Bearer Token authentication is the de facto standard for most REST APIs. It involves sending a cryptic string, known as a token, in the Authorization header. The word “Bearer” implies that anyone who possesses this token can use it to gain access.
Why it works this way: The token is typically obtained through a separate login process (OAuth flow, API key generation, etc.). This decouples authentication from the application logic. The server can validate the token, check its scope and expiration, without needing to handle a username and password on every request. This is more secure and scalable.
import httpx
# The token is often a long, randomly generated string.
api_token = "abc123yourapitoken456def"
# Method 1: Using the `headers` parameter
headers = {"Authorization": f"Bearer {api_token}"}
response = httpx.get("https://api.example.com/protected/resource", headers=headers)
# Method 2: Using a custom Auth class for reusability
class BearerAuth(httpx.Auth):
def __init__(self, token):
self.token = token
def auth_flow(self, request):
request.headers["Authorization"] = f"Bearer {self.token}"
yield request
# Use the custom auth class
with httpx.Client(auth=BearerAuth(api_token)) as client:
response = client.get("https://api.example.com/protected/resource")
Common Pitfall: The security of this method hinges entirely on the secrecy of the token. Tokens must be stored securely (e.g., in environment variables, not hardcoded in source code) and transmitted only over HTTPS. They should also be refreshed according to the API’s policy.
OAuth 2.0 (Client Credentials Flow)
OAuth 2.0 is a comprehensive authorization framework. The Client Credentials flow is used for machine-to-machine (M2M) communication where a specific user’s context is not needed. The application authenticates with its own credentials to get an access token, which is then used as a Bearer token.
Why it works this way: This flow is designed for servers or back-end services that need to access their own resources, not a user’s. It avoids the need for user interaction and is simpler than other OAuth flows.
import httpx
client_id = "your_client_id"
client_secret = "your_client_secret"
token_url = "https://authorization-server.com/token"
# 1. First, obtain the access token.
auth_response = httpx.post(
token_url,
data={"grant_type": "client_credentials"},
auth=(client_id, client_secret)
)
auth_response.raise_for_status()
token_data = auth_response.json()
access_token = token_data["access_token"]
# 2. Now, use the access token to access the protected resource.
api_url = "https://api.service.com/data"
headers = {"Authorization": f"Bearer {access_token}"}
response = httpx.get(api_url, headers=headers)
response.raise_for_status()
data = response.json()
Edge Case & Best Practice: Always handle token expiration. The response from the authorization server usually includes an expires_in field. Implement logic to cache the token and refresh it before it expires. Blindly using a cached token until a 401 error occurs and then re-authenticating is a common pattern, but proactive refreshing is more efficient for high-volume applications.