Step 9 - Security & Context (Auth & Scopes)
Implement JWT authentication and bind user scopes to the ZContext for dynamic field pruning.
To make our Zchema field-pruning work, we need to authenticate the user, extract their permissions (scopes), and load them into the ZContext.
Define the User Protocol
ZCore does not enforce a rigid user model. You just need a class that matches the UserProtocol (has id, is_active, is_superuser). Let's define a simple mock user and an auth dependency.
Create auth.py in your root directory:
# auth.py
import uuid
from zcore import BaseAuth, ctx
from pydantic import BaseModel
class AppUser(BaseModel):
id: uuid.UUID
is_active: bool = True
is_superuser: bool = False
scopes: list[str] = []
# Binds securely to ZCore's context manager
all_restricted_fields: list[str] = []
class AppAuth(BaseAuth[AppUser]):
async def fetch_user(self, identity: str) -> AppUser | None:
# In a real app, fetch from DB. Here, we mock a user.
return AppUser(
id=uuid.UUID(identity),
scopes=["tasks:view"],
# This user CANNOT view the assignee_email
all_restricted_fields=["tasks.view.assignee_email"]
)Dynamic User Caching: BaseAuth automatically caches resolved user sessions to eliminate redundant database hits. The default cache lifespan is dynamically configured via settings.AUTH_CACHE_TTL (Default: 300s) or can be passed explicitly via cache_ttl.
Override the Auth Stub
In main.py, bind this auth class to ZCore's dependency override system:
# main.py
from zcore.security import get_current_user_stub
from auth import AppAuth, AppUser
app.dependency_overrides[get_current_user_stub] = AppAuth(user_schema=AppUser)Authentication Requirement:
Because BaseAuth validates the incoming Bearer token automatically, any incoming request must provide a valid JWT token signed by Security.create_jwt() containing the user's ID as the sub claim and type="access".
Optional Authentication & Polymorphic IDs:
UserProtocol.idaccepts arbitrary ID types (int,str, oruuid.UUID).- For public routes that optionally accept authentication (like public task boards or guest checkouts), configure
BaseAuth(user_schema=AppUser, auto_error=False)and overrideget_optional_user_stubinstead ofget_current_user_stub.
How Context Shielding Activates
The Flow:
- A request hits a
BaseRouterendpoint. HasScopesdependency checks if the user hastasks:view.BaseAuth.__call__decodes the JWT, fetches the user (with cache verification), and automatically mapsall_restricted_fieldstoctx.restricted_fieldsas an immutablefrozenset.- The endpoint returns a
TaskResponseobject. Zchemaintercepts serialization, sees the restriction, and removesassignee_emailfrom the JSON response automatically.
Our API is now secure. The final step is to write a test to prove everything works.