Authentication¶
pytest-routes provides flexible authentication support for testing protected API endpoints.
Overview¶
Many APIs require authentication. pytest-routes supports:
Bearer Token Authentication - JWT tokens, OAuth2 access tokens
API Key Authentication - Header or query parameter-based keys
Composite Authentication - Multiple auth methods combined
Per-Route Overrides - Different auth for different routes
Quick Start¶
Bearer Token (Most Common)¶
# pyproject.toml
[tool.pytest-routes.auth]
bearer_token = "$API_TOKEN" # Read from environment variable
API_TOKEN=your-secret-token uv run pytest --routes --routes-app myapp:app
API_TOKEN=your-secret-token pytest --routes --routes-app myapp:app
API Key¶
# pyproject.toml
[tool.pytest-routes.auth]
api_key = "$API_KEY"
header_name = "X-API-Key"
Authentication Providers¶
BearerTokenAuth¶
Adds an Authorization: Bearer <token> header to all requests.
Configuration:
[tool.pytest-routes.auth]
bearer_token = "your-token"
# Or from environment variable (recommended)
bearer_token = "$API_TOKEN"
Programmatic Usage:
# conftest.py
import pytest
from pytest_routes import RouteTestConfig, BearerTokenAuth
@pytest.fixture
def routes_config():
return RouteTestConfig(
auth=BearerTokenAuth("your-secret-token"),
max_examples=50,
)
APIKeyAuth¶
Sends an API key via header or query parameter.
Header-based (default):
[tool.pytest-routes.auth]
api_key = "$API_KEY"
header_name = "X-API-Key" # Optional, defaults to X-API-Key
Query parameter-based:
[tool.pytest-routes.auth]
api_key = "$API_KEY"
query_param = "api_key"
Programmatic Usage:
from pytest_routes import RouteTestConfig, APIKeyAuth
# Header-based
config = RouteTestConfig(
auth=APIKeyAuth("my-api-key", header_name="X-API-Key"),
)
# Query parameter-based
config = RouteTestConfig(
auth=APIKeyAuth("my-api-key", query_param="api_key"),
)
CompositeAuth¶
Combine multiple authentication methods (e.g., Bearer token + tenant ID).
# conftest.py
from pytest_routes import RouteTestConfig, BearerTokenAuth, APIKeyAuth, CompositeAuth
@pytest.fixture
def routes_config():
return RouteTestConfig(
auth=CompositeAuth([
BearerTokenAuth("$JWT_TOKEN"),
APIKeyAuth("tenant-123", header_name="X-Tenant-ID"),
]),
)
This sends both:
Authorization: Bearer <token>X-Tenant-ID: tenant-123
NoAuth¶
Explicitly disable authentication (useful for overrides).
from pytest_routes import RouteTestConfig, NoAuth
config = RouteTestConfig(auth=NoAuth())
Environment Variables¶
Authentication tokens should never be hardcoded. Use environment variables:
[tool.pytest-routes.auth]
bearer_token = "$API_TOKEN" # Reads from API_TOKEN env var
The $ prefix tells pytest-routes to read from the environment.
Example workflow:
export API_TOKEN=$(vault read -field=token secret/api)
pytest --routes
# .github/workflows/test.yml
env:
API_TOKEN: ${{ secrets.API_TOKEN }}
steps:
- run: pytest --routes --routes-app myapp:app
Per-Route Overrides¶
Different routes may require different authentication. Use route overrides:
# pyproject.toml
# Default auth for most routes
[tool.pytest-routes.auth]
bearer_token = "$USER_TOKEN"
# Admin routes need admin credentials
[[tool.pytest-routes.routes]]
pattern = "/admin/*"
[tool.pytest-routes.routes.auth]
bearer_token = "$ADMIN_TOKEN"
# Public routes - skip auth
[[tool.pytest-routes.routes]]
pattern = "/public/*"
# No auth section = no authentication
# Internal routes - skip testing entirely
[[tool.pytest-routes.routes]]
pattern = "/internal/*"
skip = true
Override Priority¶
Route-specific override (if pattern matches)
Global
[tool.pytest-routes.auth]No authentication
Pytest Markers¶
Use markers for test-level authentication control:
@pytest.mark.routes_skip¶
Skip route smoke testing for specific tests:
import pytest
@pytest.mark.routes_skip(reason="Requires external service")
def test_external_integration():
...
@pytest.mark.routes_auth¶
Specify authentication for specific tests:
import pytest
from pytest_routes import BearerTokenAuth
@pytest.mark.routes_auth(BearerTokenAuth("special-token"))
def test_special_endpoint():
...
Example: Testing an Authenticated API¶
Here’s a complete example of testing a Litestar API with authentication:
Application (app.py):
from litestar import Litestar, get
from litestar.middleware import AuthenticationMiddleware
@get("/public")
async def public_endpoint() -> dict:
return {"message": "Public data"}
@get("/protected", guards=[require_auth])
async def protected_endpoint() -> dict:
return {"message": "Protected data"}
@get("/admin", guards=[require_admin])
async def admin_endpoint() -> dict:
return {"message": "Admin data"}
app = Litestar(
route_handlers=[public_endpoint, protected_endpoint, admin_endpoint],
middleware=[AuthenticationMiddleware],
)
Configuration (pyproject.toml):
[tool.pytest-routes]
app = "app:app"
max_examples = 50
exclude = ["/health", "/docs"]
# Default auth for protected routes
[tool.pytest-routes.auth]
bearer_token = "$USER_TOKEN"
# Admin routes need elevated privileges
[[tool.pytest-routes.routes]]
pattern = "/admin*"
[tool.pytest-routes.routes.auth]
bearer_token = "$ADMIN_TOKEN"
# Public routes - no auth needed
[[tool.pytest-routes.routes]]
pattern = "/public*"
# Omit auth = no authentication sent
Running Tests:
export USER_TOKEN=user-jwt-token
export ADMIN_TOKEN=admin-jwt-token
uv run pytest --routes -v
export USER_TOKEN=user-jwt-token
export ADMIN_TOKEN=admin-jwt-token
pytest --routes -v
Troubleshooting¶
Environment Variable Not Found¶
If you see ValueError: Environment variable 'X' is not set:
Ensure the variable is exported:
export API_TOKEN=...Check for typos in the variable name
In CI, verify secrets are configured
Wrong Auth for Route¶
Check route override patterns:
# Patterns are matched in order - first match wins
[[tool.pytest-routes.routes]]
pattern = "/api/v1/*" # Matches /api/v1/users
[[tool.pytest-routes.routes]]
pattern = "/api/*" # Would NOT match /api/v1/users if above matches first
API Reference¶
The authentication module provides:
AuthProvider- Base class for custom providersBearerTokenAuth- Bearer token implementationAPIKeyAuth- API key implementationCompositeAuth- Combine multiple providersNoAuth- Explicit no-auth provider
All classes are exported from pytest_routes and pytest_routes.auth.