A complete implementation of Mutual TLS (mTLS) authentication in FastAPI, providing bidirectional certificate-based authentication between client and server.
Mutual TLS (mTLS) extends the standard TLS protocol by requiring both the client and server to authenticate each other using X.509 certificates. Unlike standard TLS where only the server presents a certificate, mTLS ensures both parties verify each other's identity.
- Enhanced Security: Bidirectional authentication prevents unauthorized access
- Zero Trust Architecture: Implements "never trust, always verify" principles
- API Security: Ensures only authorized clients can access your APIs
- Compliance: Meets regulatory requirements for secure communications
fastapi-mtls/
βββ generate_certs.sh # Certificate generation script
βββ main.py # FastAPI application with mTLS
βββ server.py # Alternative server configuration
βββ client.py # Client examples for testing
βββ requirements.txt # Python dependencies
βββ Dockerfile # Docker container configuration
βββ docker-compose.yml # Docker Compose setup
βββ docker-entrypoint.sh # Container entrypoint script
βββ certs/ # Certificate directory (generated)
βββ ca/ # Certificate Authority
βββ server/ # Server certificates
βββ client/ # Client certificates
First, generate the required certificates for mTLS:
# Make the script executable
chmod +x generate_certs.sh
# Generate all certificates
./generate_certs.shThis creates:
- CA Certificate: Root certificate authority
- Server Certificate: For the FastAPI server
- Client Certificate: For client authentication
pip install -r requirements.txtOption A: Direct Python execution
python main.pyOption B: Using the server script (recommended)
python server.pyOption C: Using Docker
# Build and run with Docker Compose
docker-compose up --buildThe server will start on https://localhost:8443
Option A: Using the Python client
python client.pyOption B: Using curl
# Test the health endpoint
curl -k --cert ./certs/client/client-cert.pem --key ./certs/client/client-key.pem https://localhost:8443/health
# Test the secure endpoint
curl -k --cert ./certs/client/client-cert.pem --key ./certs/client/client-key.pem https://localhost:8443/secure| Endpoint | Description | Authentication Required |
|---|---|---|
/ |
Root endpoint with basic info | No |
/health |
Health check endpoint | No* |
/secure |
Secure endpoint requiring mTLS | Yes |
/cert-info |
Display client certificate info | Yes |
*Note: In production, even health checks should require authentication.
| Variable | Description | Default |
|---|---|---|
SSL_CERT_DIR |
Directory containing certificates | ./certs |
SERVER_HOST |
Server bind address | 0.0.0.0 |
SERVER_PORT |
Server port | 8443 |
LOG_LEVEL |
Logging level | INFO |
The implementation uses these security settings:
- Minimum TLS Version: 1.2
- Client Certificates: Required (
CERT_REQUIRED) - Certificate Verification: Full chain validation
- Cipher Suites: High-security ciphers only
- Use Strong Key Sizes: Minimum 2048-bit RSA or 256-bit ECDSA
- Regular Rotation: Rotate certificates before expiration
- Secure Storage: Store private keys with restricted permissions (600)
- Certificate Revocation: Implement CRL or OCSP checking
- CA Security: Keep CA private key offline and secure
- Reverse Proxy: Use nginx/HAProxy for SSL termination
- Certificate Validation: Validate certificate chains and expiration
- Monitoring: Monitor certificate expiry and SSL handshake failures
- Network Security: Use VPCs and security groups
- Secrets Management: Use HashiCorp Vault or AWS Secrets Manager
# β
Good: Validate certificates at multiple layers
def validate_certificate(cert_data: bytes) -> bool:
# Check certificate chain
# Check expiration
# Check revocation status
# Check certificate policies
pass
# β Bad: Skip certificate validation
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONEdocker build -t fastapi-mtls .# Start the service
docker-compose up -d
# View logs
docker-compose logs -f fastapi-mtls
# Health check
docker-compose exec fastapi-mtls python -c "
import requests
response = requests.get('https://localhost:8443/health',
cert=('./certs/client/client-cert.pem', './certs/client/client-key.pem'),
verify='./certs/ca/ca-cert.pem')
print(f'Status: {response.status_code}')
print(f'Response: {response.json()}')
"# Use specific tag for production
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# With nginx reverse proxy
docker-compose --profile with-nginx up -dError: Certificate file not found
Solution:
# Ensure certificates are generated
./generate_certs.sh
# Check file permissions
ls -la certs/*/Error: SSL: CERTIFICATE_VERIFY_FAILED
Possible Causes:
- Certificate expired
- Certificate not signed by trusted CA
- Hostname mismatch
- Client certificate not provided
Debug Steps:
# Check certificate validity
openssl x509 -in certs/server/server-cert.pem -text -noout
# Test SSL connection
openssl s_client -connect localhost:8443 -cert certs/client/client-cert.pem -key certs/client/client-key.pem -CAfile certs/ca/ca-cert.pemError: Permission denied: 'certs/server/server-key.pem'
Solution:
# Fix certificate permissions
chmod 600 certs/*/\*-key.pem
chmod 644 certs/*/\*-cert.pemError: Container fails to start Debug Steps:
# Check container logs
docker-compose logs fastapi-mtls
# Run container interactively
docker-compose run --rm fastapi-mtls bash
# Verify certificate mounting
docker-compose exec fastapi-mtls ls -la /app/certs/Enable debug logging:
# In main.py or server.py
logging.basicConfig(level=logging.DEBUG)# Enable SSL debug in OpenSSL
export SSLKEYLOGFILE=ssl-keys.log
export OPENSSL_CONF=/dev/null
# Run with verbose SSL logging
python -c "
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
# Your mTLS code here
"# Use connection pooling for better performance
import urllib3
http = urllib3.PoolManager(
cert_file='./certs/client/client-cert.pem',
key_file='./certs/client/client-key.pem',
ca_certs='./certs/ca/ca-cert.pem'
)# Cache certificate validation results
from functools import lru_cache
@lru_cache(maxsize=1000)
def validate_certificate_cached(cert_fingerprint: str) -> bool:
# Validation logic here
pass# Install Apache Bench with SSL support
apt-get install apache2-utils
# Load test mTLS endpoint
ab -n 1000 -c 10 -f TLS1.2 \
-Z ./certs/client/client-cert.pem \
-Z ./certs/client/client-key.pem \
https://localhost:8443/healthThis project is licensed under the MIT License. See the LICENSE file for details.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request