Authentication Decorators¶
Authentication decorators provide route-level authentication and authorization controls. These decorators help ensure secure communication and proper authentication for sensitive endpoints.
HTTPS Enforcement¶
Force secure connections for specific routes:
. Basic HTTPS Requirement¶
from guard import SecurityDecorator
guard_deco = SecurityDecorator(config)
@app.post("/api/login")
@guard_deco.require_https()
def login(credentials: dict):
return {"token": "secure_jwt_token"}
. Combined with Global HTTPS¶
# Global HTTPS enforcement
config = SecurityConfig(enforce_https=True)
# Route-specific override (still enforced due to global setting)
@app.get("/api/public")
@guard_deco.require_https() # Explicit requirement
def public_endpoint():
return {"data": "definitely secure"}
. HTTPS for Sensitive Operations¶
@app.post("/api/payment")
@guard_deco.require_https()
def payment_endpoint(payment_data: dict):
return {"status": "payment processed securely"}
@app.post("/api/user/password")
@guard_deco.require_https()
def change_password(password_data: dict):
return {"status": "password updated"}
Authentication Requirements¶
Enforce different types of authentication:
Verifier Contract and Fail-Closed Behavior¶
Since guard-core 3.13.0 (fastapi-guard 7.7.0), require_auth and api_key_auth are real authentication. A request only passes when a verifier accepts the credential. A verifier is a callable with the signature:
requestis the guard request. The FastAPIRequestyou inject in the endpoint shares the samestateobject, so anything the verifier stashes is visible in the endpoint.credentialis the extracted credential: the bearer/basic token forrequire_auth, or the API-key header value forapi_key_auth.- Return a truthy principal (any object) to accept. The principal is stashed on
request.state.auth_principaland is readable in the endpoint. - Return
None(or any falsy value) to reject with 401. - Raise, and the request is rejected with 401. Verifiers must not leak exception details to the client.
In ASGI (fastapi-guard) the verifier may be sync or async; async verifiers are awaited. A verifier is resolved per route: a verifier= argument to require_auth or api_key_auth overrides the global SecurityConfig.auth_verifier. If neither is set, the request is rejected with 401 fail-closed. A bare Bearer/Basic prefix or any API-key header value no longer passes on its own.
Set a global verifier once and every decorated route uses it:
config = SecurityConfig(
auth_verifier=lambda request, credential: {"user": "demo"} if credential else None,
)
guard_deco = SecurityDecorator(config)
The fenced examples below assume that global verifier is set, unless they pass verifier= explicitly.
. Bearer Token Authentication¶
async def verify_bearer(request, credential):
if credential == "valid-token":
return {"user": "alice", "scopes": ["read"]}
return None
@app.get("/api/profile")
@guard_deco.require_auth(type="bearer", verifier=verify_bearer)
def user_profile():
return {"profile": "user data"}
The principal returned by the verifier is available in the endpoint on request.state.auth_principal:
from fastapi import Request
@app.get("/api/me")
@guard_deco.require_auth(type="bearer", verifier=verify_bearer)
def who_am_i(request: Request):
return {"principal": request.state.auth_principal}
. Multiple Authentication Types¶
@app.get("/api/admin")
@guard_deco.require_auth(type="bearer")
def admin_endpoint():
return {"admin": "data"}
@app.get("/api/service")
@guard_deco.require_auth(type="basic")
def service_endpoint():
return {"service": "data"}
. Combined HTTPS and Auth¶
@app.post("/api/secure-admin")
@guard_deco.require_https()
@guard_deco.require_auth(type="bearer")
def secure_admin():
return {"data": "doubly secure"}
Presence-Only Header Gate (NOT Authentication)¶
If you only ever wanted a header-presence and scheme-prefix gate, and do not need authentication, use require_authorization_header. It is the renamed old behavior of require_auth: it checks that the Authorization header is present and starts with the given scheme, and nothing more. No verifier is consulted, no principal is stashed, and request.state.auth_principal is unset. A missing Authorization header is rejected with 401.
@app.get("/api/presence")
@guard_deco.require_authorization_header(scheme="bearer")
def presence_gate():
return {"data": "header was present"}
require_authorization_header is mutually exclusive with require_auth and api_key_auth. Combining them on the same route raises ValueError at decoration time, because a presence-only gate is not authentication and an authenticated route must not also be gated as presence-only.
API Key Authentication¶
Require API keys for endpoint access:
. Basic API Key Requirement¶
@app.get("/api/key-protected")
@guard_deco.api_key_auth(header_name="X-API-Key")
def api_key_endpoint():
return {"data": "api key required"}
. Custom Header Names¶
@app.get("/api/custom-key")
@guard_deco.api_key_auth(header_name="X-Custom-Auth")
def custom_key_endpoint():
return {"data": "custom header auth"}
@app.get("/api/service-key")
@guard_deco.api_key_auth(header_name="Authorization-Key")
def service_key_endpoint():
return {"data": "service authentication"}
. Multiple Key Requirements¶
@app.get("/api/dual-auth")
@guard_deco.api_key_auth(header_name="X-API-Key")
@guard_deco.api_key_auth(header_name="X-Service-Key")
def dual_auth_endpoint():
return {"data": "dual key authentication"}
Required Headers¶
Enforce specific headers for authentication and security:
. Security Headers¶
@app.get("/api/secure")
@guard_deco.require_headers(
{"X-Requested-With": "XMLHttpRequest", "X-CSRF-Token": "required"}
)
def secure_endpoint():
return {"data": "csrf protected"}
. API Versioning Headers¶
@app.get("/api/v2/data")
@guard_deco.require_headers(
{"Accept": "application/vnd.api+json", "API-Version": "2.0"}
)
def versioned_endpoint():
return {"data": "version 2.0", "format": "json-api"}
. Client Identification¶
@app.get("/api/client-specific")
@guard_deco.require_headers(
{
"X-Client-ID": "required",
"X-Client-Version": "required",
"User-Agent": "required",
}
)
def client_endpoint():
return {"data": "client identified"}
Combined Authentication Patterns¶
Stack multiple authentication decorators for comprehensive security:
. Maximum Security Endpoint¶
@app.post("/api/admin/critical")
@guard_deco.require_https() # Secure connection
@guard_deco.require_auth(type="bearer") # Bearer token
@guard_deco.api_key_auth(header_name="X-Admin-Key") # Admin API key
@guard_deco.require_headers(
{
"X-CSRF-Token": "required", # CSRF protection
"X-Request-ID": "required", # Request tracking
}
)
def critical_admin_endpoint():
return {"status": "critical operation completed"}
. Service-to-Service Authentication¶
@app.post("/api/service/webhook")
@guard_deco.require_https()
@guard_deco.api_key_auth(header_name="X-Service-Key")
@guard_deco.require_headers(
{
"X-Signature": "required", # Webhook signature
"Content-Type": "application/json",
}
)
def webhook_endpoint():
return {"status": "webhook processed"}
. Client Application Authentication¶
@app.get("/api/mobile/data")
@guard_deco.require_https()
@guard_deco.require_auth(type="bearer")
@guard_deco.require_headers(
{
"X-App-Version": "required",
"X-Device-ID": "required",
"Accept": "application/json",
}
)
def mobile_endpoint():
return {"data": "mobile app data"}
Authentication Flow Examples¶
. Login Endpoint¶
@app.post("/auth/login")
@guard_deco.require_https()
@guard_deco.require_headers(
{"Content-Type": "application/json", "X-CSRF-Token": "required"}
)
def login(credentials: dict):
# Validate credentials
return {"token": "jwt_token", "expires": "3600"}
. Token Refresh¶
@app.post("/auth/refresh")
@guard_deco.require_https()
@guard_deco.require_auth(type="bearer")
@guard_deco.require_headers({"X-Refresh-Token": "required"})
def refresh_token():
return {"token": "new_jwt_token", "expires": "3600"}
. Logout¶
@app.post("/auth/logout")
@guard_deco.require_auth(type="bearer")
@guard_deco.require_headers({"X-CSRF-Token": "required"})
def logout():
return {"status": "logged out"}
API Gateway Pattern¶
Different authentication for different API tiers:
. Public API¶
@app.get("/api/public/status")
@guard_deco.api_key_auth(header_name="X-Public-Key")
def public_status():
return {"status": "public api active"}
. Partner API¶
@app.get("/api/partner/data")
@guard_deco.require_https()
@guard_deco.api_key_auth(header_name="X-Partner-Key")
@guard_deco.require_headers({"X-Partner-ID": "required"})
def partner_data():
return {"data": "partner exclusive"}
. Internal API¶
@app.get("/api/internal/admin")
@guard_deco.require_https()
@guard_deco.require_auth(type="bearer")
@guard_deco.api_key_auth(header_name="X-Internal-Key")
@guard_deco.require_headers(
{"X-Service-Name": "required", "X-Request-Context": "required"}
)
def internal_admin():
return {"data": "internal admin access"}
Migration from pre-7.7.0¶
Before 7.7.0, require_auth(type="bearer") and api_key_auth(header_name=...) accepted any Bearer/Basic prefix or any API-key header value without validation. That behavior was an authentication check in name only. As of 7.7.0 those routes return 401 until a verifier is wired. There is no deprecation runway; the break is immediate. Do one of the following:
. Add a verifier per route¶
Pass a verifier= callable that validates the credential and returns a principal:
@guard_deco.require_auth(type="bearer", verifier=verify_bearer)
def protected():
return {"data": "authenticated"}
. Set a global verifier¶
Set SecurityConfig.auth_verifier once and every decorated route uses it unless a per-route verifier= overrides it:
. Switch to a presence-only gate¶
If you only wanted a header-presence/scheme-prefix gate and do not need authentication, switch to require_authorization_header. It preserves the old behavior exactly:
@guard_deco.require_authorization_header(scheme="bearer")
def presence_gate():
return {"data": "header was present"}
Options 1 and 2 turn the decorator into real authentication. Option 3 keeps the old presence-only behavior.
Error Handling¶
Authentication decorators return specific HTTP status codes:
- 400 Bad Request: Missing required headers
- 401 Unauthorized: Missing or invalid authentication, no verifier configured, or the verifier denied the credential (raised or returned a falsy value)
- 403 Forbidden: Valid auth but insufficient permissions
- 301/302 Redirect: HTTP to HTTPS redirect
. Custom Error Responses¶
config = SecurityConfig(
custom_error_responses={
400: "Missing required authentication headers",
401: "Invalid authentication credentials",
403: "Insufficient privileges for this operation",
}
)
Best Practices¶
. Layer Authentication Methods¶
Use multiple authentication factors for sensitive operations:
# Good: Multiple authentication layers
@guard_deco.require_https()
@guard_deco.require_auth(type="bearer")
@guard_deco.api_key_auth(header_name="X-API-Key")
# Avoid: Single authentication method for sensitive data
# @guard_deco.api_key_auth(header_name="X-API-Key") # Too weak for sensitive ops
. Always Use HTTPS for Authentication¶
Never transmit credentials over unencrypted connections:
# Good: HTTPS enforced for login
@guard_deco.require_https()
@guard_deco.require_auth(type="bearer")
# Bad: Authentication without HTTPS
# @guard_deco.require_auth(type="bearer") # Credentials could be intercepted
. Validate Header Content¶
Do not stop at presence. require_auth and api_key_auth run the verifier you wire, so the verifier is the validation site for the credential. require_headers only checks presence, so for any header whose value matters, pair it with a verifier or validate the value in the endpoint:
@guard_deco.api_key_auth(header_name="X-API-Key", verifier=verify_api_key)
def api_key_endpoint():
return {"data": "verified"}
require_authorization_header is presence-only by design and does not validate the credential; use it only when presence is all you need.
. Use Appropriate Authentication for Each Endpoint¶
Match authentication strength to data sensitivity:
# Public data: Light authentication
@guard_deco.api_key_auth(header_name="X-Public-Key")
# User data: Medium authentication
@guard_deco.require_auth(type="bearer")
# Admin data: Heavy authentication
@guard_deco.require_https()
@guard_deco.require_auth(type="bearer")
@guard_deco.api_key_auth(header_name="X-Admin-Key")
Integration with FastAPI Security¶
Combine decorators with FastAPI's built-in security:
from fastapi import Depends, HTTPException
from fastapi.security import HTTPBearer
security = HTTPBearer()
@app.get("/api/integrated")
@guard_deco.require_https()
@guard_deco.require_headers({"X-Client-ID": "required"})
def integrated_endpoint(token: str = Depends(security)):
# FastAPI handles token extraction
# Decorators handle additional security
return {"data": "integrated security"}
Testing Authentication¶
Test your authentication decorators:
import pytest
from fastapi.testclient import TestClient
def test_https_required():
# Should redirect HTTP to HTTPS
response = client.get("/api/secure", base_url="http://testserver")
assert response.status_code == 301
def test_api_key_required():
# Should reject without API key
response = client.get("/api/key-protected")
assert response.status_code == 400
# Should accept with valid API key
response = client.get("/api/key-protected", headers={"X-API-Key": "valid-key"})
assert response.status_code == 200
Next Steps¶
Now that you understand authentication decorators, explore other security features:
- Access Control Decorators - IP and geographic restrictions
- Rate Limiting Decorators - Request rate controls
- Behavioral Analysis - Monitor authentication patterns
- Content Filtering - Request validation
For complete API reference, see the Authentication API Documentation.