Building Authorization Infrastructure with SpiceDB, Go and Kubernetes
Treating permissions as first-class platform infrastructure
Authorization is infrastructure, not a feature toggle
Most teams start authorization with scattered conditionals: if is_admin, ownership checks, and endpoint-specific role lists. It works for a while, then complexity ramps and nobody can confidently answer what a user can do across the whole system. I have seen this become one of the biggest hidden sources of product and security risk.
My view is simple: permissions should be treated as infrastructure. If your product is multi-tenant, collaboration-heavy, or API-rich, authorization deserves a dedicated model, explicit schema, deployment strategy, and observability just like any other core platform subsystem.
This write-up covers a practical approach using SpiceDB, Go services, and Kubernetes, with an emphasis on rollout safety and operational discipline.
Why Zanzibar-style authorization helps
Zanzibar-style systems model access as relationships rather than static roles hardcoded per endpoint. Instead of embedding policy logic in dozens of services, you centralize relationship definitions and permission checks through a consistent authorization layer.
That gives you several benefits:
- clear policy definitions that can be reasoned about and reviewed
- consistent check semantics across services
- ability to evolve permissions without rewriting every handler
- traceable decisions for debugging and audit workflows
SpiceDB provides a strong implementation path for this model, especially when you need expressive relationships and maintainable policy growth.
Schema-as-code as the baseline
The biggest win is treating your authorization schema like application code. Keep it versioned, reviewed, and promoted through environments with the same rigor as backend services.
A schema-as-code workflow typically includes:
- schema files in source control
- reviewed pull requests for permission model changes
- validation in CI
- explicit migrations between schema versions
Without this discipline, authorization drift happens quickly. Teams edit rules ad hoc, and eventually nobody is sure which behaviors are intentional versus accidental.
Modeling relationships and capabilities
I usually start with a capability matrix before writing schema definitions. The matrix maps actors, resources, and operations into explicit expectations. Once the matrix exists, schema design is much easier because each permission has a concrete reason to exist.
A good model separates:
- resource relationships (owner, viewer, editor, member)
- derived permissions (can_read, can_write, can_admin)
- context boundaries (organization, project, dataset, document)
This structure makes policy intent readable and helps product teams discuss changes without diving into service code.
Go service integration patterns
In Go services, I prefer a thin authorization client layer that wraps SpiceDB calls with typed helpers. Handlers should not compose raw tuple logic repeatedly. They should call clear methods like CanViewPortfolio() or CanManageInstitution() and receive a deterministic allow/deny decision with structured error handling.
A useful integration pattern is:
- middleware extracts subject identity and tenant context
- service handler builds resource reference
- auth client performs check with timeout and request id
- decision and metadata are logged in structured form
Keep timeouts strict. Authorization checks sit on critical request paths, so slow calls can cascade into broad latency spikes.
Kubernetes deployment strategy
SpiceDB deployment in Kubernetes should be treated as stateful infrastructure with clear SLOs. I usually define:
- dedicated deployment resources and autoscaling policy
- readiness checks tied to dependency health
- resource requests/limits aligned with expected check volume
- network policy and secret management for least privilege
If your platform spans regions, decide early whether checks stay region-local or cross-region. That choice affects both latency and consistency assumptions.
Shadow-mode rollout to reduce risk
The safest way to introduce a new authorization layer into existing services is shadow mode. In shadow mode, your current access logic remains the source of truth while SpiceDB checks run in parallel. You compare outcomes and collect mismatches before enforcing new decisions.
A practical rollout sequence:
- phase 1: run shadow checks and log comparison results only
- phase 2: alert on mismatch rates above thresholds
- phase 3: enforce SpiceDB for low-risk endpoints
- phase 4: expand enforcement to full surface area
This approach catches model gaps early and avoids abrupt access regressions for users.
Testing beyond happy paths
Authorization testing should include more than endpoint unit tests. I typically run four layers:
- schema tests: expected allow/deny behavior for core relationship cases.
- service tests: handler behavior when auth client allows, denies, times out, or errors.
- integration tests: end-to-end checks against a test SpiceDB instance.
- migration tests: compatibility checks when schema evolves.
Regression tests are critical for negative paths. Many incidents come from accidentally broadening permissions, not from denying too much.
Observability and operational signals
Good auth infrastructure is observable infrastructure. I monitor:
- authorization check latency percentiles
- allow/deny rates per service and endpoint
- error and timeout rates
- shadow mismatch counts during rollout
- schema version adoption across environments
I also attach request and subject identifiers (where appropriate and compliant) so incidents can be traced quickly. When a user reports lost access, you should be able to answer "which check failed and why" in minutes, not days.
Migrations and schema evolution
Permission models evolve as products evolve. New resource types appear, ownership semantics change, and teams split or merge. Plan for evolution from day one:
- introduce new relations alongside old ones
- backfill tuples safely with idempotent jobs
- dual-read during migration windows when needed
- remove deprecated relations only after metrics confirm zero usage
Trying to do destructive permission migrations in one step is usually where outages happen.
Common pitfalls
A few pitfalls repeat in almost every authorization migration:
- mixing business policy and technical policy with no ownership boundaries
- shipping schema updates without corresponding service-level tests
- treating timeouts as implicit allow behavior
- underestimating how much product UX depends on clear permission outcomes
Another frequent issue is weak naming. If relation and permission names are vague, policy review becomes guesswork. Invest in clear domain language early.
How this connects to AI and data-heavy systems
As teams build more AI infrastructure, authorization complexity increases. Systems now include model outputs, internal tools, document stores, and multi-step workflows with different access requirements. If permissions are scattered, AI features can accidentally expose data across boundaries.
A centralized, relationship-based authorization layer gives you a safer base to scale AI products. It keeps checks consistent as new services, agents, and data pipelines are added over time.
Closing thoughts
Authorization should not be an afterthought patched into handlers one endpoint at a time. It should be designed, deployed, and observed like core backend infrastructure. SpiceDB, Go, and Kubernetes provide a solid foundation, but the real value comes from operating it with discipline: schema-as-code, shadow rollout, rigorous testing, and clear observability.
If your team is feeling permission complexity now, that is usually the signal to invest in infrastructure, not more conditionals.
Tags: Joshua Fields, software engineer, SpiceDB, Zanzibar authorization, Go, Kubernetes, backend infrastructure, AI infrastructure, London
Back to Writing | Joshua Fields Home | Industry Experience | GitHub | LinkedIn
Joshua Fields — full portfolio