Implementing Fine-Grained Access Control with Ory Keto
Decouple authorization from your code using Ory Keto's relation-based access control (ReBAC). Learn how to use relation tuples and the Check API to manage fine-grained permissions.
17 Oct 2025, 01:40 UTC

Hardcoding permissions into application logic—such as if (user.role == 'admin')—creates a maintenance burden as your resource hierarchy grows. The useful takeaway is to decouple authorization from your business logic by using Ory Keto, which implements a relation-based access control (ReBAC) model. Instead of checking roles, you check if a specific relationship exists between a subject and an object.
Understanding the Relation-Based Model
Ory Keto is inspired by Google's Zanzibar. Unlike traditional Role-Based Access Control (RBAC), which assigns permissions to roles and roles to users, ReBAC defines permissions based on relationships. For example, instead of an "Editor" role, you define a relationship where User:Alice is an editor of Document:42.
These relationships are stored as relation tuples. A tuple typically consists of a namespace (the type of resource), the object identifier, the relation (the permission or role), and the subject (the user or group). This allows for deeply nested permissions, such as a user having access to a document because they are a member of a folder that contains that document.
The Check API: Verifying Access
To determine if a user can perform an action, your application queries Keto's Check API. This API does not evaluate complex logic scripts at runtime; instead, it traverses the graph of relation tuples to find a path from the subject to the object via the requested relation.
Because Keto focuses on the existence of a relationship, the evaluation is deterministic and highly performant, provided the relationship graph is not excessively deep. This moves the "decision" from a code-level conditional to a data-level query.
Worked Example: Checking a Relationship
To verify if a user has access to a resource, you send a request to the /check endpoint. Run this command from a terminal with network access to your Keto instance. This example assumes you have already defined a namespace for "documents" and created a tuple linking the user to the document.
curl -X POST http://keto:4466/check \
-H "Content-Type: application/json" \
-d '{
"namespace": "Document",
"object": "document_id_123",
"permissions": ["view"],
"subjects": [{"id": "user_id_abc"}]
}'
Configuration Details:
namespace: The resource type defined in your Keto configuration.object: The unique identifier for the specific resource instance.permissions: A list of relations you are checking for (e.g., "view", "edit", "owner").subjects: The identity of the user or group requesting access.
Expected Result: Keto returns a JSON response indicating whether the relationship is allowed. A successful check confirms that a valid path exists in the relation graph. Risk: If the namespace is not correctly configured in the Keto configuration file, the API will return an error regardless of the tuples present in the database.
Storage and Performance Trade-offs
Keto primarily relies on SQL databases (such as PostgreSQL or MySQL) to store relation tuples. The choice of storage impacts how you scale:
- Consistency: Using a centralized SQL store ensures that permission changes (adding/removing tuples) are propagated consistently across all API instances.
- Latency: While the Check API is optimized, very complex relationship chains (e.g., user → group → sub-group → folder → document) can increase query time.
- Availability: Because Keto is a critical path for every request, the database becomes a single point of failure. Implementing a caching layer at the application level for frequent checks is a common practical mitigation.
Limitations and Verification
Ory Keto is designed for authorization (can this user do this?), not authentication (who is this user?). It must be paired with an identity provider like Ory Kratos. Additionally, Keto does not natively support "attribute-based" logic (e.g., "allow access only if the user is in the US and it is between 9 AM and 5 PM") without external logic in your application code.
To verify your implementation, you can use the /relation-tuples/write endpoint to manually inject a test tuple, then call the /check endpoint to ensure the access is granted. Finally, delete the tuple and verify that the /check endpoint now returns a denial.
Actionable Integration Path
- Map your resources: Identify your namespaces (e.g., Organization, Project, File) and the relations between them.
- Define the schema: Configure the Keto namespace definitions to allow for inherited permissions (e.g., an "owner" of a project is automatically a "viewer" of all files in that project).
- Implement Tuple Writing: Add logic to your application to write a relation tuple whenever a user is assigned a role or a resource is created.
- Replace Hardcoded Checks: Replace
if (user.isAdmin)blocks with calls to the Keto Check API.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.