Centralizing Fine-Grained Access with Ory Keto in Microservices
Microservices duplicate permission logic across code. Centralizing fine-grained access with Ory Keto’s ABAC policy engine decouples decisions from services and integrates with OAuth2/OIDC flows.
11 Nov 2025, 09:40 UTC

Microservice APIs end up with permission checks scattered across code: one service checks roles in middleware, another duplicates the same logic in a handler, and a third hard-codes an allow-list. The result is inconsistent access, slow audits, and changes that require redeploys. The useful takeaway is to move the decision out of the services and into a central policy engine that evaluates an access request from attributes in a token, not from code paths.
Ory Keto provides an Attribute-Based Access Control, ABAC, policy engine that can centralize fine-grained permissions, decouple policy from code, and integrate with existing OAuth2/OIDC flows. ABAC means a decision is based on attributes like who the subject is, what resource is targeted, and what action is requested, rather than only a static role.
ABAC vs RBAC in practice
Role-Based Access Control, RBAC, assigns users to roles and roles to permissions. It is simple until you need context: a manager should read financial records only for their own department and only during business hours. With RBAC you end up creating many narrow roles. ABAC keeps a small set of roles and expresses conditions as policy rules.
Keto models access as relation tuples in namespaces: namespace:object:relation. Policies are stored centrally and evaluated at request time. That separation lets product teams change who can do what without touching service code.
How Keto fits a service call
A typical flow is: client presents an OAuth2 access token, the API gateway validates the token and extracts claims, then the service asks Keto whether the subject is allowed to perform an action on a resource.
Token validation happens in the service or gateway with the expected OIDC verifier permissions. Policy evaluation is a separate network call to Keto’s check endpoint. The service should treat a denied decision as a 403 and should never fall back to allow on error.
Risk to note: exposing the policy or check endpoints without authentication can leak the model of your permissions. The check endpoint should be reachable only from internal networks and require mTLS or service tokens.
Worked example: manager read on financial resource
Assume a namespace finance with objects like report:Q1. A relation tuple can express that a user with role manager is allowed read on a financial object.
A minimal policy concept for this scenario is: allow read if the subject has role manager and the resource type is financial. In Keto terms this is expressed as relation tuples and optional conditions rather than imperative code.
From a service you would build a check request using attributes from the token, e.g., subject id from sub, resource id from the URL, and action from the HTTP method. A representative curl shape run from an internal runner with network access to Keto and a service token is:
curl -X POST https://keto.internal/check \
-H "Authorization: Bearer $SERVICE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"subject": {"id": "user_123", "roles": ["manager"]},
"resource": {"namespace": "finance", "object": "report:Q1", "type": "financial"},
"action": "read"
}'Expected checks are that the request reaches Keto, the policy is evaluated, and the service receives an allow or deny decision to enforce. Do not assume a specific response shape; verify against your deployed Keto version’s API documentation.
Performance and operational limits
Policy evaluation adds a network round-trip. For latency-sensitive paths cache decisions briefly keyed by subject, resource, action and relevant attributes, and invalidate on policy changes.
Keto’s default in-memory store is convenient for development but is not suitable for production. Plan for a distributed store early and avoid relying on local state for consistency.
Complex expressions become hard to debug. Keep rules short and composable, name them clearly, and avoid deep nesting of conditions.
A practical way to check correctness is to enable audit logging for decisions, replay a sample of denied requests, and confirm that no unintended access is granted. Instrument the service to log policy decisions with request correlation ids and monitor decision latency against your SLA threshold.
Actionable next steps
Start with one low-risk service. Define a minimal policy set for a single resource and action. Use a local development deployment for iteration, then migrate to a shared durable backend before production.
Monitor decision latency and audit logs to validate correctness. Review policies regularly for over-permissive rules, especially after role changes.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.