This commit is contained in:
Bahnish 2025-12-16 21:07:32 +00:00 committed by GitHub
commit 361a9af0cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 304 additions and 0 deletions

View File

@ -0,0 +1,108 @@
# API Key Authentication
There are many ways to handle security, authentication and authorization.
But let's imagine that you have your **backend** API and you want to have a simple way to authenticate requests using an **API key** in an HTTP header.
This is very common for APIs that provide services to other applications or microservices.
## API Key in Header
FastAPI provides `APIKeyHeader` to handle API key authentication using HTTP headers.
Let's look at how to implement this:
{* ../../docs_src/security/tutorial_api_key_header.py *}
## What it does
When you create an instance of `APIKeyHeader`:
```Python
api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
```
* `name`: The name of the HTTP header that will contain the API key
* `auto_error`: If `True` (default), FastAPI will automatically return an error if the header is missing. If `False`, the dependency will return `None` when the header is missing.
## The dependency
When you use `api_key_header` as a dependency:
```Python
def verify_api_key(api_key: str = Security(api_key_header)):
```
FastAPI will:
1. Look for a header with the name you specified (in this case `X-API-Key`)
2. Extract the value from that header
3. Pass it as the `api_key` parameter to your function
## Verification logic
In the `verify_api_key` function:
* We check if the API key is present
* We verify that it matches our expected value
* If invalid, we raise an `HTTPException` with status code 401
* If valid, we return the API key (or could return user information)
## Using the protected endpoint
To access the protected endpoint, clients need to include the API key in the request header:
```bash
curl -H "X-API-Key: your-secret-api-key" http://localhost:8000/protected
```
Without the header:
```bash
curl http://localhost:8000/protected
# Returns: 401 Unauthorized
```
With an invalid API key:
```bash
curl -H "X-API-Key: wrong-key" http://localhost:8000/protected
# Returns: 401 Unauthorized
```
## Interactive documentation
When you go to `/docs`, you will see that your endpoints are marked as requiring authentication, and there's a way to set the API key for testing:
1. Click the "Authorize" button
2. Enter your API key in the `APIKeyHeader` field
3. Click "Authorize"
4. Now you can test the protected endpoints directly from the docs
## Multiple API Key methods
You can also combine different authentication methods. FastAPI provides:
* `APIKeyQuery`: API key in a query parameter
* `APIKeyHeader`: API key in an HTTP header (shown above)
* `APIKeyCookie`: API key in a cookie
You can use them individually or combine them for more flexible authentication.
## Real-world considerations
In a production environment, you should:
1. **Store API keys securely**: Use environment variables or a secure key management system
2. **Use strong API keys**: Generate long, random strings
3. **Implement key rotation**: Allow keys to be updated periodically
4. **Add rate limiting**: Prevent abuse of your API
5. **Log access**: Monitor who is using your API and how
6. **Use HTTPS**: Always encrypt traffic containing API keys
## Next steps
This is a basic example of API key authentication. For more complex scenarios, you might want to:
* Store API keys in a database with associated user information
* Implement different permission levels for different keys
* Add expiration dates to API keys
* Combine API key authentication with other methods like OAuth2

View File

@ -103,4 +103,10 @@ FastAPI provides several tools for each of these security schemes in the `fastap
In the next chapters you will see how to add security to your API using those tools provided by **FastAPI**.
You will learn about:
* **API Key Authentication**: Simple authentication using API keys in headers, query parameters, or cookies
* **OAuth2 with Password (and hashing), Bearer with JWT tokens**: Full OAuth2 implementation with JWT tokens
* **Login system**: How to create a complete login system
And you will also see how it gets automatically integrated into the interactive documentation system.

View File

@ -0,0 +1,82 @@
from fastapi import Depends, FastAPI, HTTPException, Security, status
from fastapi.security import APIKeyHeader
from pydantic import BaseModel
app = FastAPI()
# Configuration de l'API Key - normalement vous stockeriez cela de manière sécurisée
API_KEY = "your-secret-api-key"
API_KEY_NAME = "X-API-Key"
api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
class User(BaseModel):
username: str
role: str
def verify_api_key(api_key: str = Security(api_key_header)):
"""
Vérifie si l'API key fournie est valide.
Args:
api_key: L'API key extraite de l'en-tête HTTP
Returns:
L'API key si elle est valide
Raises:
HTTPException: Si l'API key est manquante ou invalide
"""
if api_key is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="API Key manquante",
headers={"WWW-Authenticate": "API-Key"},
)
if api_key != API_KEY:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="API Key invalide",
headers={"WWW-Authenticate": "API-Key"},
)
return api_key
def get_current_user(api_key: str = Depends(verify_api_key)):
"""
Retourne l'utilisateur actuel basé sur l'API key valide.
Dans un vrai système, vous feriez une requête à votre base de données
pour récupérer l'utilisateur associé à l'API key.
"""
# Simulation d'une recherche d'utilisateur en base
return User(username="john_doe", role="admin")
@app.get("/")
async def public_endpoint():
"""Point d'accès public - aucune authentification requise."""
return {"message": "Ceci est un endpoint public"}
@app.get("/protected")
async def protected_endpoint(current_user: User = Depends(get_current_user)):
"""
Point d'accès protégé - nécessite une API key valide.
Pour tester cet endpoint:
curl -H "X-API-Key: your-secret-api-key" http://localhost:8000/protected
"""
return {
"message": f"Bonjour {current_user.username}!",
"user_role": current_user.role,
"protected_data": "Données sensibles accessibles uniquement avec une API key valide",
}
@app.get("/users/me")
async def read_current_user(current_user: User = Depends(get_current_user)):
"""Récupère les informations de l'utilisateur actuel."""
return current_user

View File

@ -0,0 +1,21 @@
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import APIKeyHeader
app = FastAPI()
API_KEY = "your-secret-api-key"
api_key_header = APIKeyHeader(name="X-API-Key")
def verify_api_key(api_key: str = Depends(api_key_header)):
if api_key != API_KEY:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid API Key"
)
return api_key
@app.get("/protected")
def read_protected_data(api_key: str = Depends(verify_api_key)):
return {"message": "This is protected data", "api_key": api_key}

View File

@ -0,0 +1,87 @@
from fastapi.testclient import TestClient
from docs_src.security.tutorial_api_key_header import app
client = TestClient(app)
def test_public_endpoint():
"""Test que l'endpoint public fonctionne sans authentification."""
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Ceci est un endpoint public"}
def test_protected_endpoint_with_valid_api_key():
"""Test de l'endpoint protégé avec une API key valide."""
response = client.get("/protected", headers={"X-API-Key": "your-secret-api-key"})
assert response.status_code == 200
data = response.json()
assert "message" in data
assert "john_doe" in data["message"]
assert data["user_role"] == "admin"
assert "protected_data" in data
def test_protected_endpoint_without_api_key():
"""Test de l'endpoint protégé sans API key."""
response = client.get("/protected")
assert response.status_code == 401
assert response.json() == {"detail": "API Key manquante"}
def test_protected_endpoint_with_invalid_api_key():
"""Test de l'endpoint protégé avec une API key invalide."""
response = client.get("/protected", headers={"X-API-Key": "wrong-key"})
assert response.status_code == 401
assert response.json() == {"detail": "API Key invalide"}
def test_users_me_endpoint_with_valid_api_key():
"""Test de l'endpoint /users/me avec une API key valide."""
response = client.get("/users/me", headers={"X-API-Key": "your-secret-api-key"})
assert response.status_code == 200
data = response.json()
assert data["username"] == "john_doe"
assert data["role"] == "admin"
def test_users_me_endpoint_without_api_key():
"""Test de l'endpoint /users/me sans API key."""
response = client.get("/users/me")
assert response.status_code == 401
def test_openapi_schema():
"""Test que le schéma OpenAPI inclut bien la sécurité API Key."""
response = client.get("/openapi.json")
assert response.status_code == 200
openapi_schema = response.json()
# Vérifier que le composant de sécurité API Key est présent
assert "components" in openapi_schema
assert "securitySchemes" in openapi_schema["components"]
security_schemes = openapi_schema["components"]["securitySchemes"]
# Rechercher le scheme APIKeyHeader
api_key_scheme = None
for _scheme_name, scheme_data in security_schemes.items():
if scheme_data.get("type") == "apiKey" and scheme_data.get("in") == "header":
api_key_scheme = scheme_data
break
assert api_key_scheme is not None
assert api_key_scheme["name"] == "X-API-Key"
# Vérifier que les endpoints protégés ont bien la sécurité définie
paths = openapi_schema["paths"]
# L'endpoint /protected devrait avoir de la sécurité
protected_endpoint = paths["/protected"]["get"]
assert "security" in protected_endpoint
# L'endpoint public ne devrait pas avoir de sécurité
public_endpoint = paths["/"]["get"]
assert "security" not in public_endpoint or public_endpoint.get("security") == []