How do I perform basic HTTP authentication with Requests?
HTTP Basic Authentication is one of the most common authentication methods used by web APIs and protected web resources. The Python Requests library provides several convenient ways to handle basic authentication, making it straightforward to access protected endpoints that require username and password credentials.
Understanding HTTP Basic Authentication
HTTP Basic Authentication sends credentials (username and password) encoded in Base64 format within the Authorization
header. The format follows the pattern: Authorization: Basic <base64-encoded-credentials>
. While simple to implement, it's important to note that Basic Authentication should only be used over HTTPS connections to ensure credential security.
Basic Authentication Methods with Requests
Method 1: Using the auth Parameter (Recommended)
The most straightforward approach is using the auth
parameter with a tuple containing your username and password:
import requests
# Basic authentication using auth parameter
response = requests.get(
'https://api.example.com/protected-endpoint',
auth=('username', 'password')
)
if response.status_code == 200:
data = response.json()
print("Authentication successful!")
print(data)
elif response.status_code == 401:
print("Authentication failed - check credentials")
else:
print(f"Request failed with status code: {response.status_code}")
Method 2: Using HTTPBasicAuth Class
For more explicit control, you can use the HTTPBasicAuth
class from requests.auth
:
from requests.auth import HTTPBasicAuth
import requests
# Using HTTPBasicAuth class
auth = HTTPBasicAuth('username', 'password')
response = requests.get(
'https://api.example.com/protected-endpoint',
auth=auth
)
print(f"Status Code: {response.status_code}")
print(f"Response: {response.text}")
Method 3: Manual Header Construction
You can also manually construct the Authorization header, though this is generally not recommended as it's more error-prone:
import requests
import base64
# Manual header construction (not recommended)
username = 'your_username'
password = 'your_password'
# Create the base64 encoded credentials
credentials = f"{username}:{password}"
encoded_credentials = base64.b64encode(credentials.encode()).decode()
headers = {
'Authorization': f'Basic {encoded_credentials}',
'Content-Type': 'application/json'
}
response = requests.get(
'https://api.example.com/protected-endpoint',
headers=headers
)
Practical Examples and Use Cases
Example 1: API Access with Error Handling
Here's a robust example that includes proper error handling and response validation:
import requests
from requests.exceptions import RequestException, HTTPError
def make_authenticated_request(url, username, password):
"""
Make an authenticated HTTP request with comprehensive error handling
"""
try:
response = requests.get(
url,
auth=(username, password),
timeout=30, # Add timeout for reliability
verify=True # Ensure SSL certificate verification
)
# Raise an exception for HTTP error status codes
response.raise_for_status()
return response.json()
except HTTPError as http_err:
if response.status_code == 401:
print("Authentication failed: Invalid credentials")
elif response.status_code == 403:
print("Access forbidden: Insufficient permissions")
else:
print(f"HTTP error occurred: {http_err}")
except RequestException as req_err:
print(f"Request error occurred: {req_err}")
except ValueError as json_err:
print(f"JSON decode error: {json_err}")
return None
# Usage example
api_data = make_authenticated_request(
'https://api.example.com/data',
'your_username',
'your_password'
)
if api_data:
print("Data retrieved successfully:", api_data)
Example 2: Session-Based Authentication
For multiple requests to the same API, using a session is more efficient:
import requests
# Create a session with persistent authentication
session = requests.Session()
session.auth = ('username', 'password')
# Add common headers
session.headers.update({
'User-Agent': 'MyApp/1.0',
'Accept': 'application/json'
})
# Make multiple requests using the same session
try:
# First request
response1 = session.get('https://api.example.com/endpoint1')
print(f"First request status: {response1.status_code}")
# Second request - authentication is automatically included
response2 = session.get('https://api.example.com/endpoint2')
print(f"Second request status: {response2.status_code}")
# POST request with data
post_data = {'key': 'value'}
response3 = session.post('https://api.example.com/submit', json=post_data)
print(f"POST request status: {response3.status_code}")
finally:
# Always close the session
session.close()
Security Best Practices
Environment Variables for Credentials
Never hardcode credentials in your source code. Use environment variables instead:
import os
import requests
# Get credentials from environment variables
username = os.getenv('API_USERNAME')
password = os.getenv('API_PASSWORD')
if not username or not password:
raise ValueError("API credentials not found in environment variables")
response = requests.get(
'https://api.example.com/protected',
auth=(username, password)
)
Set environment variables in your shell:
export API_USERNAME="your_username"
export API_PASSWORD="your_password"
python your_script.py
Using Configuration Files
For development environments, you can use configuration files (ensure they're in .gitignore
):
import json
import requests
# Load credentials from config file
with open('config.json', 'r') as config_file:
config = json.load(config_file)
auth_credentials = (config['username'], config['password'])
response = requests.get(
config['api_url'],
auth=auth_credentials
)
Advanced Authentication Scenarios
Conditional Authentication
Sometimes you need to handle both authenticated and non-authenticated requests:
import requests
def api_request(url, username=None, password=None):
"""
Make API request with optional authentication
"""
kwargs = {'timeout': 30}
if username and password:
kwargs['auth'] = (username, password)
response = requests.get(url, **kwargs)
return response
# Public endpoint (no auth required)
public_data = api_request('https://api.example.com/public')
# Protected endpoint (auth required)
private_data = api_request(
'https://api.example.com/private',
username='user',
password='pass'
)
Handling Authentication with Proxies
When working with proxies, you might need both proxy authentication and basic authentication:
import requests
# Authentication for both proxy and target server
proxies = {
'http': 'http://proxy_user:proxy_pass@proxy.example.com:8080',
'https': 'https://proxy_user:proxy_pass@proxy.example.com:8080'
}
response = requests.get(
'https://api.example.com/protected',
auth=('api_username', 'api_password'), # API authentication
proxies=proxies, # Proxy with authentication
verify=True
)
Integration with Other Tools
While basic authentication with Requests works well for API endpoints, some authentication flows require browser interaction. For complex authentication scenarios like OAuth2 flows or JavaScript-heavy login processes, you might need to combine Requests with browser automation tools. Tools like Puppeteer can handle authentication flows that require user interaction or JavaScript execution before making API calls with Requests.
Common Issues and Troubleshooting
Issue 1: 401 Unauthorized Errors
import requests
response = requests.get(
'https://api.example.com/protected',
auth=('username', 'password')
)
if response.status_code == 401:
print("Possible issues:")
print("1. Incorrect username or password")
print("2. Account may be locked or disabled")
print("3. API requires different authentication method")
print("4. Check if credentials need URL encoding")
Issue 2: SSL Certificate Problems
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
# Disable SSL warnings (not recommended for production)
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
# For development/testing only - disable SSL verification
response = requests.get(
'https://api.example.com/protected',
auth=('username', 'password'),
verify=False # Only for development!
)
# Better approach: specify custom CA bundle
response = requests.get(
'https://api.example.com/protected',
auth=('username', 'password'),
verify='/path/to/ca-bundle.crt'
)
Issue 3: Special Characters in Credentials
If your username or password contains special characters, ensure proper handling:
import requests
from urllib.parse import quote
# URL encode credentials if they contain special characters
username = quote('user@domain.com')
password = quote('p@ssw0rd!')
response = requests.get(
'https://api.example.com/protected',
auth=(username, password)
)
Testing Authentication
Unit Testing with Mock
import requests
from unittest.mock import patch, Mock
def test_authenticated_request():
with patch('requests.get') as mock_get:
# Mock successful authentication
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {'data': 'success'}
mock_get.return_value = mock_response
# Your function that makes authenticated requests
result = make_authenticated_request(
'https://api.example.com/test',
'test_user',
'test_pass'
)
# Verify the request was made with correct auth
mock_get.assert_called_once_with(
'https://api.example.com/test',
auth=('test_user', 'test_pass'),
timeout=30,
verify=True
)
assert result == {'data': 'success'}
Performance Considerations
For high-frequency API calls, consider these optimizations:
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
# Configure session with connection pooling and retries
session = requests.Session()
session.auth = ('username', 'password')
# Configure retry strategy
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504]
)
# Mount adapter with retry strategy
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
session.mount("https://", adapter)
# Set connection pooling
session.mount("https://", HTTPAdapter(pool_connections=20, pool_maxsize=20))
Real-World API Examples
GitHub API Authentication
import requests
# GitHub API requires basic auth for private repositories
username = 'your_github_username'
token = 'your_personal_access_token' # Use token instead of password
response = requests.get(
'https://api.github.com/user/repos',
auth=(username, token),
headers={'Accept': 'application/vnd.github.v3+json'}
)
if response.status_code == 200:
repos = response.json()
for repo in repos:
print(f"Repository: {repo['name']}")
REST API with Basic Auth
import requests
import os
def fetch_customer_data(customer_id):
"""
Fetch customer data from CRM API
"""
api_base = 'https://api.crm-system.com'
username = os.getenv('CRM_USERNAME')
password = os.getenv('CRM_PASSWORD')
response = requests.get(
f'{api_base}/customers/{customer_id}',
auth=(username, password),
headers={
'Accept': 'application/json',
'User-Agent': 'MyApp/1.0'
},
timeout=10
)
if response.status_code == 200:
return response.json()
elif response.status_code == 404:
return None
else:
response.raise_for_status()
# Usage
customer = fetch_customer_data('12345')
if customer:
print(f"Customer: {customer['name']}")
Working with Multiple Endpoints
When dealing with multiple APIs that require different authentication, organize your code for maintainability:
import requests
from typing import Dict, Optional
class AuthenticatedAPIClient:
"""
A reusable client for API endpoints requiring basic authentication
"""
def __init__(self, base_url: str, username: str, password: str):
self.base_url = base_url.rstrip('/')
self.session = requests.Session()
self.session.auth = (username, password)
self.session.headers.update({
'Accept': 'application/json',
'Content-Type': 'application/json'
})
def get(self, endpoint: str, params: Optional[Dict] = None) -> requests.Response:
"""Make GET request to endpoint"""
url = f"{self.base_url}/{endpoint.lstrip('/')}"
return self.session.get(url, params=params)
def post(self, endpoint: str, data: Optional[Dict] = None) -> requests.Response:
"""Make POST request to endpoint"""
url = f"{self.base_url}/{endpoint.lstrip('/')}"
return self.session.post(url, json=data)
def close(self):
"""Close the session"""
self.session.close()
# Usage example
client = AuthenticatedAPIClient(
'https://api.example.com',
'username',
'password'
)
try:
response = client.get('/users/profile')
if response.status_code == 200:
profile = response.json()
print(f"User: {profile['name']}")
finally:
client.close()
Command Line Tools
For testing authentication from the command line, you can create utility scripts:
#!/usr/bin/env python3
"""
Command line tool for testing API authentication
Usage: python auth_test.py <url> <username> <password>
"""
import sys
import requests
from requests.auth import HTTPBasicAuth
def test_auth(url, username, password):
"""Test basic authentication against a URL"""
try:
response = requests.get(
url,
auth=HTTPBasicAuth(username, password),
timeout=10
)
print(f"Status Code: {response.status_code}")
print(f"Response Headers: {dict(response.headers)}")
if response.status_code == 200:
print("✓ Authentication successful")
return True
elif response.status_code == 401:
print("✗ Authentication failed - check credentials")
return False
else:
print(f"✗ Unexpected status code: {response.status_code}")
return False
except requests.exceptions.RequestException as e:
print(f"✗ Request failed: {e}")
return False
if __name__ == "__main__":
if len(sys.argv) != 4:
print("Usage: python auth_test.py <url> <username> <password>")
sys.exit(1)
url, username, password = sys.argv[1:4]
success = test_auth(url, username, password)
sys.exit(0 if success else 1)
Browser Session Integration
Sometimes you need to extract session data from browser automation tools and use it with Requests. This approach is useful when initial authentication requires browser interaction, but subsequent API calls can use Requests for better performance:
import requests
def use_browser_session_with_requests(cookies_from_browser):
"""
Use cookies obtained from browser automation for API requests
"""
session = requests.Session()
# Add cookies from browser session
for cookie in cookies_from_browser:
session.cookies.set(cookie['name'], cookie['value'])
# Now make API calls with the authenticated session
response = session.get('https://api.example.com/protected-data')
return response.json()
This pattern works well when combined with browser automation tools for handling complex authentication that establish the initial session.
Conclusion
HTTP Basic Authentication with Python Requests is straightforward and secure when implemented correctly. Key takeaways:
- Use the
auth
parameter - It's the most convenient and reliable method - Always use HTTPS - Basic authentication sends credentials in an easily decoded format
- Store credentials securely - Use environment variables or secure configuration management
- Implement proper error handling - Check status codes and handle network exceptions
- Use sessions for multiple requests - More efficient for repeated API calls
- Test thoroughly - Verify authentication works across different scenarios
The auth
parameter is the recommended approach for most use cases, while sessions are ideal for multiple requests to the same API. For more complex scenarios involving browser automation or JavaScript-heavy authentication flows, consider combining Requests with browser automation tools.
Remember to test your authentication thoroughly and monitor for changes in the API's authentication requirements, as these can evolve over time.