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**.
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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