Designing a Minimal Delegated Authorization Flow in SpiceDB
Learn how to let services act on behalf of users in SpiceDB with scoped JWTs, trust boundaries, and operational safeguards. A step‑by‑step guide to a minimal delegated authorization architecture.
21 Jul 2025, 08:19 UTC

Problem
Modern micro‑service architectures often need a service to act on behalf of a user while keeping the user’s long‑term credentials out of the service’s trust domain. SpiceDB can enforce fine‑grained policies, but it requires a way to prove the user’s identity and the set of actions the service is allowed to perform. The challenge is to build a lightweight, stateless delegation mechanism that integrates cleanly with SpiceDB’s policy model and preserves operational safety.
Requirements
- Services must be able to request a short‑lived token that encodes the user’s identity and a scoped set of actions.
- SpiceDB must validate the token against its policy store without storing per‑token state.
- Key material for signing tokens must be protected and rotatable.
- Revocation and auditability must be supported through policy updates and logs.
- The design should minimize the number of moving parts and avoid introducing a persistent token store.
Smallest Suitable Design
The minimal architecture consists of three components:
- Delegation Service – a lightweight service that authenticates users (e.g., via OAuth2) and issues signed JWTs containing a
delegation_scopeclaim. - SpiceDB Node – the policy engine that receives requests from client services and verifies the JWT against its policy definitions.
- Client Service – the service that performs actions on behalf of the user, attaching the JWT to each request.
All communication is over HTTPS; the Delegation Service and SpiceDB share a public key infrastructure. No token is stored server‑side; the token’s validity is determined by its signature, expiration, and scope.
Trust & Data Boundaries
Delegation Service trusts the authentication provider and holds the signing key. It should run with the principle of least privilege, exposing only the /delegate endpoint. The key is stored in a secure vault (e.g., HashiCorp Vault, AWS KMS). Key rotation requires re‑issuing tokens; the Delegation Service can support a /rotate endpoint that invalidates old keys by updating the kid (key ID) in the JWT header.
SpiceDB trusts the public key of the Delegation Service. It never stores the signing key. Each request includes the JWT; SpiceDB verifies the signature using the public key and then checks the delegation_scope claim against the policy store. Policy updates propagate to all nodes via the standard replication mechanism.
Client Service never holds user passwords or long‑term secrets. It only keeps the JWT in memory for the duration of the request and discards it afterward.
Operational Checks
| Check | Description | Implementation |
|---|---|---|
| Token Expiration | Ensure exp claim is within a short window (e.g., 5 minutes). | SpiceDB rejects any token with exp < current time. |
| Scope Validation | Token must contain only allowed actions. | Policy definition uses delegation_scope: read:project,write:project and SpiceDB matches against this list. |
| Revocation via Policy | Revoking a scope should invalidate existing tokens. | Update policy to remove the scope; SpiceDB’s cache invalidation triggers a re‑check on next request. |
| Audit Logging | Record delegation issuance and usage. | Delegation Service logs user_id, scopes, client_id, issued_at. SpiceDB logs request_id and the evaluated decision. |
Failure Modes & Mitigations
- Token Forgery – Mitigated by strong asymmetric signing (e.g., RS256) and short token lifetimes.
- Replay Attacks – Short
expand inclusion of anonceclaim that SpiceDB can optionally check against a cache. - Stale Policy Data – Ensure policy replication latency is within the token lifetime; use
max_ageheader in SpiceDB requests to force fresh data if needed. - Key Rotation Failures – Keep a short overlap period where both old and new keys are accepted; the Delegation Service includes
kidand SpiceDB holds the corresponding public keys.
Concrete Example
Policy Definition (YAML)
# delegation_policy.yaml
- type: delegation
id: project:read_write
relations:
- relation: read
constraint: delegation_scope == "read:project"
- relation: write
constraint: delegation_scope == "write:project"
- type: project
id: "*"
relations:
- relation: owner
constraint: ...
Delegation Service – Token Request
Run the delegation service with the following command (placeholder values):
./delegation-service \
--auth-provider=oauth2 \
--sign-key=/secrets/private_key.pem \
--public-key=/secrets/public_key.pem \
--listen=0.0.0.0:8443
To request a token for user alice with read/write scopes:
curl -X POST https://delegation-service:8443/delegate \
-H "Authorization: Bearer <oauth2_token>" \
-d '{"scopes": ["read:project", "write:project"], "audience": "spicedb"}'
The response contains a JWT:
{"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjEyMzQ1NiJ9.eyJzdWIiOiJhbGljZSIsICJkZWxpZ2F0aW9uX3Njb3JlIjpbInJlYWQ6cHJvamVjdCIsICJ3cml0ZTpwcm9qZWN0Il0sICJleHAiOjE2NzYwODAwMDB9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"}
Client Service – Using the Token
Assuming the client wants to list projects:
# gRPC client code (pseudo)
import spicedb
client = spicedb.Client("spicedb:50051")
headers = {
"authorization": "Bearer <JWT from delegation service>"
}
response = client.list_projects(headers=headers)
print(response)
SpiceDB will validate the JWT, extract the delegation_scope claim, and evaluate it against the delegation_policy.yaml definitions. If the scope includes read:project, the request succeeds; otherwise SpiceDB returns an UNAUTHENTICATED error.
Verification Checklist
- Issue a token with the correct scopes and perform an allowed action – confirm success.
- Issue a token lacking a required scope and attempt the same action – confirm SpiceDB rejects with
UNAUTHENTICATED. - Update the delegation policy to remove a scope, wait for replication (or use
max_age=0), and retry the action – confirm failure. - Rotate the signing key, re‑issue a token, and verify the old token is still rejected.
Conclusion
By combining signed JWTs with scoped delegation policies, SpiceDB can provide stateless, fine‑grained authorization for services acting on behalf of users. The architecture keeps the trust boundary tight, relies on SpiceDB’s built‑in policy engine for decision making, and offers clear operational safeguards. The minimal design scales horizontally, avoids persistent token storage, and aligns with common security best practices.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.