Usage Guide: Identity¶
Overview¶
This guide shows how to configure the Zeroth service's authentication layer and how authenticated principals flow through the rest of the runtime. The data shapes come from zeroth.governance.identity; the verification and HTTP plumbing come from zeroth.service.api.authentication.
See Concept: identity for the model and Concept: service for how routes enforce roles.
Minimal example¶
from zeroth.governance.identity import ServiceRole
from zeroth.service.api.authentication import (
BearerTokenConfig,
ServiceAuthConfig,
StaticApiKeyCredential,
)
from zeroth.service.bootstrap.factory import bootstrap_service
# 1. Configure one static API key and (optionally) a JWT bearer verifier.
auth_config = ServiceAuthConfig(
api_keys=[
StaticApiKeyCredential(
credential_id="ops-1",
secret="demo-operator-key", # replace with a real secret
subject="ops@example.com",
roles=[ServiceRole.OPERATOR, ServiceRole.REVIEWER],
tenant_id="acme",
workspace_id="prod",
)
],
bearer=BearerTokenConfig(
issuer="https://auth.example.com/",
audience="zeroth-core",
jwks_url="https://auth.example.com/.well-known/jwks.json",
),
)
# 2. Hand the config to bootstrap_service; routes now enforce it.
service = await bootstrap_service(
database,
deployment_ref=deployment.deployment_ref,
auth_config=auth_config,
executable_unit_runner=runner,
)
# 3. Configuration can also be loaded from the environment.
from_env = ServiceAuthConfig.from_env() # reads ZEROTH_SERVICE_API_KEYS_JSON + bearer
Once the service is bootstrapped, every request must send either X-API-Key: demo-operator-key or Authorization: Bearer <jwt>. The verifier returns an AuthenticatedPrincipal; .to_actor() downgrades it to the ActorIdentity that gets stamped onto every run, approval, and audit record.
Common patterns¶
- Tenant scoping. Set
tenant_idon each credential; the orchestrator usesprincipal.scope()to ensure tenant data never leaks across deployments. - Role-based route checks.
OPERATORruns graphs,REVIEWERresolves approvals,ADMINmutates config — mix roles on a credential when one subject needs more than one capability. - Env-driven config in CI.
ServiceAuthConfig.from_env()readsZEROTH_SERVICE_API_KEYS_JSON/ZEROTH_SERVICE_BEARER_JSON, which keeps secrets out of YAML. - JWT + API key side-by-side. You can ship bearer auth for human SSO and keep a static API key around for automation; both paths yield the same
AuthenticatedPrincipalshape.
Pitfalls¶
- Missing
tenant_idon a credential. Defaults to"default", which is fine for single-tenant demos but invisibly collides data across tenants in production. - Shared secrets.
StaticApiKeyCredential.secretis compared viahmac.compare_digest; still, rotate them on schedule and never commit them. - JWKS not reachable.
BearerTokenConfigrequiresjwks_urlor an inlinejwks— if the URL is unreachable at verify time, every bearer request fails. - Forgetting required claims.
roles,tenant_id, andworkspace_idon a JWT are read fromclaimsand passed through verbatim; missing claims silently produce anOPERATOR-less principal that cannot run graphs. - Assuming
ActorIdentitycarries raw tokens. It does not — onlysubject,auth_method,roles, and scope. Rawclaimsstay on the request-boundAuthenticatedPrincipal.
Reference cross-link¶
See the Python API reference for zeroth.governance.identity. The related zeroth.service.api.authentication module is documented under the Service reference page.
Related: Concept: identity, Concept: service, Usage Guide: approvals, Tutorial: governance walkthrough.