Scoped, hashed API keys in sillo — APIKeyAuthBackend, ApiKey/ApiKeyManager for storage, and ApiKeyUserMixin for issuing keys from a user.
API Keys
Section titled “API Keys”API keys are for server-to-server and programmatic access: a long-lived secret sent in a header, scoped to specific permissions, and stored hashed (never plaintext). sillo gives you the full lifecycle — generate, verify, list, revoke.
1. Protecting routes with the backend
Section titled “1. Protecting routes with the backend”from sillo.auth import AuthenticationMiddleware, useAuthfrom sillo.auth.apikey import APIKeyAuthBackendfrom sillo.users import User
app.use(AuthenticationMiddleware( user_model=User, backend=APIKeyAuthBackend(header_name="X-API-Key", verify_with_manager=True),))
@app.get("/v1/data", auth=useAuth(scopes=["apikey"]))async def data(request, response): return {"called_by": request.user.identity}Backend parameters:
| Param | Default | Meaning |
|---|---|---|
header_name | "X-API-Key" | Header to read the key from. |
prefix | "key" | Expected key prefix (key is prefix_xxxx). |
verify_with_manager | False | When True, validates the key against an ApiKey DB row (checks hash, expiry, is_active). |
On success, request.scope["auth"] becomes "apikey".
2. Storing keys — ApiKey & ApiKeyManager
Section titled “2. Storing keys — ApiKey & ApiKeyManager”Keys are hashed with SHA-256 before storage. You never persist the raw key.
from sillo.auth.apikey import ApiKey, ApiKeyManager
# Generate: returns (full_key, raw, hash)full_key, raw, key_hash = await ApiKey.generate_api_key(prefix="sillo")
# Persist a row for a userawait ApiKey.create( user_id=1, name="ci-pipeline", key_hash=key_hash, scopes=["read:data", "write:data"], expires_at=None, # or datetime in the future)
# Verify a presented key against the stored hashok = ApiKey.verify_api_key(raw, key_hash)
# Look up / revoke via the managerkeys = await ApiKeyManager().get_for_user(1)await ApiKeyManager().revoke_all_for_user(1)ApiKey fields: name, key_hash (unique), user_id, scopes (JSON list), last_used_at, expires_at, is_active. Helpers: mark_used(), revoke(), and the property is_expired.
3. Issuing keys from a user — ApiKeyUserMixin
Section titled “3. Issuing keys from a user — ApiKeyUserMixin”Add ApiKeyUserMixin to your user class so a user can self-service keys:
from sillo.users import Userfrom sillo.auth.apikey.mixins import ApiKeyUserMixin
class AppUser(User, ApiKeyUserMixin): ...
user = await AppUser.load_user("1")
full_key, apikey = await user.create_api_key( name="ci", scopes=["read:data"], expires_at=None, prefix="sillo",)# full_key is the only time you see the raw secret; apikey is the DB row
keys = await user.get_api_keys() # list active key rowsawait user.revoke_api_key(key_id) # revoke oneawait user.revoke_all_api_keys() # revoke all for this usercreate_api_key returns (full_key, ApiKey) — full_key is shown once; afterwards only the hash exists in the database.
4. Scopes and the auth gate
Section titled “4. Scopes and the auth gate”The backend confirms that the key is valid but does not expose the key’s scopes on the request — so scope enforcement is your job. Re-verify the presented key in the handler to get the ApiKey row (which carries scopes):
from sillo.auth.apikey import ApiKeyManager
@app.get("/v1/data", auth=useAuth(scopes=["apikey"]))async def data(request, response): raw = request.headers.get("X-API-Key") apikey = await ApiKeyManager().verify(raw) # returns the ApiKey row or None if apikey is None or "read:data" not in (apikey.scopes or []): return response.json({"error": "insufficient scope"}, status_code=403) ...5. Combining with other backends
Section titled “5. Combining with other backends”API keys commonly sit alongside JWT/session so the same endpoints serve both humans and machines:
app.use(AuthenticationMiddleware( user_model=User, backend=[ APIKeyAuthBackend(header_name="X-API-Key", verify_with_manager=True), JWTAuthBackend(secret_key=JWT_SECRET, identifier="sub"), SessionAuthBackend(), ],))A request with X-API-Key authenticates as "apikey"; one with a bearer token as "jwt"; one with a cookie as "session". Routes can pin a method with useAuth(scopes=["apikey"]) or accept any with useAuth().
Related
Section titled “Related”- Authentication — middleware + backend model
- Protecting Routes —
useAuth(scopes=["apikey"]) - Users & User Models —
ApiKeyUserMixinwiring - JWT · Sessions