mirror of https://github.com/tiangolo/fastapi.git
Merge 3797c3ca61 into 272204c0c7
This commit is contained in:
commit
361a9af0cd
|
|
@ -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
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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}
|
||||||
|
|
@ -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") == []
|
||||||
Loading…
Reference in New Issue