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**. 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. 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") == []