Getting started with ORY Keto SDK for Java

Joshua Lindsay
daemon-engineering
Published in
6 min readFeb 15, 2022

--

Ory Keto “is the first open source implementation of Zanzibar; Google’s Consistent, Global Authorization System”. At Dae.mn, we think it could be a really useful tool for authorisation in the future so I’m currently working on a small proof-of-concept project to integrate it with a dummy Java application.

I quickly discovered that there was a shortage of documentation available for this new technology so thought I would share what I’ve learned so far.

Before we start, let’s recap what Ory Keto is:

Determining whether online users are authorized to access digital objects is central to preserving privacy. This paper presents the design, implementation, and deployment of Zanzibar, a global system for storing and evaluating access control lists. Zanzibar provides a uniform data model and configuration language for expressing a wide range of access control policies from hundreds of client services at Google, including Calendar, Cloud, Drive, Maps, Photos, and YouTube. Its authorization decisions respect causal ordering of user actions and thus provide external consistency amid changes to access control lists and object contents. Zanzibar scales to trillions of access control lists and millions of authorization requests per second to support services used by billions of people. It has maintained 95th-percentile latency of less than 10 milliseconds and availability of greater than 99.999% over 3 years of production use.

If you need to know if a user (or robot, car, service) is allowed to do something — Ory Keto is the right fit for you. https://www.ory.sh/keto/docs/next/

So, starting with trying to understand how it works I began sifting through the various pages on ORY’s page which landed me with a demo that I could quickly get up and running using Docker Desktop. I ran the following commands to bootstrap my environment:

git clone git@github.com:ory/keto.git && cd ketodocker-compose -f contrib/cat-videos-example/docker-compose.yml up
# or
./contrib/cat-videos-example/up.sh

When these commands are run, the Ory Keto Docker image is downloaded and run, then seed data is migrated into SQL-lite.

# output: all initially created relation tuples# NAMESPACE OBJECT RELATION NAME SUBJECT
# videos /cats/1.mp4 owner videos:/cats#owner
# videos /cats/1.mp4 view videos:/cats/1.mp4#owner
# videos /cats/1.mp4 view *
# videos /cats/2.mp4 owner videos:/cats#owner
# videos /cats/2.mp4 view videos:/cats/2.mp4#owner
# videos /cats owner cat lady
# videos /cats view videos:/cats#owner

Next, I then installed the Ory Keto application onto my local machines using brewso that I could interact with the running service in Docker from my terminal:

$ brew tap ory/keto
$ brew install ory/keto/keto
$ keto help

Once installed, I then set a Keto remote read endpoint environment variable:

export KETO_READ_REMOTE=”127.0.0.1:4466"

This gave me a baseline implementation that I could make simple calls to and return a result. For example:

# Is “*” allowed to “view” the object “videos”:”/cats/2.mp4"?
keto check “*” view videos /cats/2.mp4
# output:
# Denied

The next step was to investigate how I could integrate Ory Keto with a Spring Boot application using the SDK for Java. The first problem I hit was with the documentation…there is none!

Unfortunately, this SDK is not yet documented.

Time to get stuck in…

To get started integrating Ory Keto with Java, my first point of call was to get the SDK plugged into my project, so in the pom.xml I added the below:

<dependency>
<groupId>sh.ory.keto</groupId>
<artifactId>keto-client</artifactId>
<version>0.7.0-alpha.1</version>
</dependency>

From this, I began to sift through the SDK’s code in GitHub to get an understanding of what methods are available and how to get a client-initiated. This provided me with the 4 main APIs and the client:

  • WriteApi
  • ReadApi
  • HealthApi
  • VersionApi
  • ApiClient

I used these APIs to create a KetoService class within my project and began by declaring the ReadApi and WriteApi classes, as these were the two API’s I was most interested in. I attached the ApiClient to both.

Having looked at my service running in Docker, I noticed that it exposed the read and write APIs on different ports, hence two different base paths here:

@Service
public class KetoService {

private final ReadApi readInstance;
private final WriteApi writeInstance;

public KetoService() {
readInstance = new ReadApi(new ApiClient().setBasePath("http://localhost:4466"));
writeInstance = new WriteApi(new ApiClient().setBasePath("http://localhost:4467"));
}
}

Check call

The first call I wanted to replicate was the initial call I made within the terminal that checked whether * had access to /cats/2.mp4within the videosnamespace.

I created a method named check that took several strings (namespace, object, relation, subjectId). If we look at call in the terminal, this is how the elements would align:

  • namespace: videos
  • object: /cats/2.mp4
  • relation: view
  • subjectId: *

I wrapped the API call in a try/catch block so that I could debug the call and inspect the return message or exception.

The readInstance.getCheck() method takes 4 required params and 3 optional. To keep it simple I set the 3 optional params to null and gave it the required params with the same values as above.

public GetCheckResponse check(String namespace, String object, String relation, String subjectId) {
try {
return readInstance.getCheck(namespace, object, relation, subjectId, null, null, null);
} catch (ApiException e) {
System.out.println(e.getResponseBody());
return null;
}
}

On success, the return type is GetCheckResponse which has an internal method of getAllowed(). This will return a boolean value of true for the above parameters. So far I haven’t managed to get the API to falsify gracefully, so I wrapped the results into an if/else block which returns a System.out.println.

GetCheckResponse policyCheck = ketoService.check("videos", "/cats/2.mp4", "view", "*");
if (policyCheck != null) {
System.out.println("Successfully returned policy check result: " + policyCheck.getAllowed());
} else {
System.out.println("Check was invalid");
}

Create call

The second call I wanted to look at was how to create relations within Keto. I used the same steps as above to figure out what was required to create a relation using the SDK.

I discovered I needed to use an object of type RelationQuery, which had the getters and setters for the params needed to create a relation. This time I need to use the WriteApi, which had the method .createRelationTuple()that took the RelationQuery object as a parameter.

On success, it returns the query you passed it but no proof of success other than that. On fail, I have set it to print the error message and return null. This also means that either the object was invalid or the relation is already in the DB.

public RelationQuery createRelation(String namespace, String object, String relation, String subjectId) {
try {
RelationQuery query = new RelationQuery();
query.setNamespace(namespace);
query.setObject(object);
query.setRelation(relation);
query.setSubjectId(subjectId);

return writeInstance.createRelationTuple(query);

} catch (ApiException e) {
System.out.println(e.getResponseBody());
return null;
}
}

Delete call

The last call I needed to get this implementation fully functioning was the ability to delete relations. The delete call is very similar to the check call, in that it takes 4 required params and 3 optional.

As before, the method is wrapped in a try/catch block to check that it is functioning as it should. There is no return type for deletion, if it is successful it will step out otherwise it will print that there is no object matching.

public void deleteRelation(String namespace, String object, String relation, String subjectId) {
try {
writeInstance.deleteRelationTuple(namespace, object, relation, subjectId, null, null, null);
} catch (ApiException e) {
System.out.println(e.getResponseBody());
}
}

Summary

There is still a lot more to understand and with the client still very much in its infancy, I don't believe this to be its final resting place in regards to the structure of the APIs. This is a basic implementation of the SDK that demonstrates some fundamentals that hopefully will give you a head start whilst the documentation is being finalised.

Sources

Joshua Lindsay is a Consultant at Dae.mn

--

--