Using Ory Keto to secure NestJS backends
Tags: opensource, webdev, security
Published: 2022-11-13 13:10:27

[Ory Keto](https://www.ory.sh/docs/keto/) is a Permissions & Access Control System based on [Zanzibar: Google’s Consistent, Global Authorization System](https://research.google/pubs/pub48190/).
It uses a graph to evaluate permissions and is therefore very flexible in its structure.
Allowed access is nothing more than an existing path in the graph.

For checking permissions it uses a structure called Relation Tuple.
The string-representation has the following syntax [(BNF)](https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_form):
```bnf
<relation-tuple> ::= <object>'#'relation'@'<subject>
<object> ::= namespace':'object_id
<subject> ::= subject_id | <subject_set>
<subject_set> ::= <object>'#'relation
```
A valid example would be `namespace:object#relation@subjectId` or `namespace:object#relation@subjectNamespace:subjectObject#subjectRelation`.
The latter can be difficult to read, therefore you'll often see the following variant: `namespace:object#relation@(subjectNamespace:subjectObject#subjectRelation)`

Relation Tuples can be verbalized.
The Relation Tuple `sharedFiles:a.txt#access@dirs:b#access` can be verbalized as "Anyone with `access` to `dirs:b` has `access` to `sharedFiles:a.txt`".

The SDKs of Ory Keto (REST and gRPC) use an object/class representation of the Relation Tuple. This is fine for most cases but not as convenient to use and understand as a simple string.

Because of this I wrote a small Typescript library to parse the string-representation of a Relation Tuple into the needed object-representation.

The general variant, not tied to Keto, is called `@nidomiro/relation-tuple-parser` ([npm](https://www.npmjs.com/package/@nidomiro/relation-tuple-parser), [github](https://github.com/nidomiro/relation-tuple-parser/tree/main/packages/typescript)).
It supports converting static strings and strings with placeholders.
The latter is especially useful if you want to substitute e.g. the id of the currently logged-in user in the Relation Tuple.

The variant for Keto `@nidomiro/relation-tuple-parser-ory-keto` ([npm](https://www.npmjs.com/package/@nidomiro/relation-tuple-parser-ory-keto), [GitHub](https://github.com/nidomiro/relation-tuple-parser/tree/main/packages/typescript-ory-keto)) just contains converters. They convert the given Relation Tuple (with, or without replacements) into a form you can use with the Keto SDK.

Let's look into a usage-example.

## Example with [NestJS](https://nestjs.com/)

I assume you already know NestJS and are in a freshly created project. Additionally, I assume that Keto is already accessible.

The full source-code of this example can be found [here](https://github.com/nidomiro/relation-tuple-parser/tree/main/packages/examples/nestjs-guard)

### What we want to accomplish

* We want an easy way to secure a backend route with Keto.
* The person developing a new route shouldn't be responsible for communication with Keto.

The routes permissions should therefore be configurable via a Decorator. The choice fell to the following syntax:
```ts
@GuardedBy(({ currentUserId }) => `groups:users#member@${currentUserId}`)
```
It is concise and flexible at the same time.
We can use it, to secure our `getData` route:

```ts
@Controller()
export class AppController {
	@GuardedBy(({ currentUserId }) => `groups:users#member@${currentUserId}`)
	@Get()
	getData() {
		return 'Access grated!'
	}
}
```

### Implementing the Decorator

How is the decorator shown above implemented?

In fact, it is relatively simple. It accepts a string or a generator function as parameter. Then it parses the given Relation Tuples and stores them inside the Handlers' metadata.
This way, the parsing happens at app-startup and not with every request.

The decorator [(full source)](https://github.com/nidomiro/relation-tuple-parser/blob/main/packages/examples/nestjs-guard/src/app/guard/guarded-by.decorator.ts):
```ts
export const GuardedBy = (relationTuple: string | RelationTupleStringGenerator<PossibleReplacements>) => {
	let valueToSet: GuardedByMetadataType
	if (typeof relationTuple === 'string') {
		valueToSet = parseRelationTupleWithReplacements(() => relationTuple).unwrapOrThrow()
	} else {
		valueToSet = parseRelationTupleWithReplacements(relationTuple).unwrapOrThrow()
	}

	return SetMetadata(GUARDED_BY_METADATA_KEY, valueToSet)
}
```
Note that both variants are stored as a `RelationTupleWithReplacements`. This way we only have to think about one variant during the evaluation.

To statically type the possible replacements we create a type:
```ts
export type PossibleReplacements = {
	currentUserId: string | 'Unauthorized'
}
```

### Implementing the (App-) Guard

The actual check of the Relation Tuple against Keto happens inside the Guard - `KetoGuard` [(full source)](https://github.com/nidomiro/relation-tuple-parser/blob/main/packages/examples/nestjs-guard/src/app/guard/keto-guard.ts) in our case.

First we need to add the Keto SDK: `npm i @ory/keto-grpc-client` and if you need the REST-client `npm i @ory/keto-client`.

We use the gRPC variant for checking, because it is a bit faster than the HTTP variant - at least in theory.

What we need to do in the Guard:
1. get the value set by the Guard (the `RelationTupleWithReplacements`)
2. get the needed replacement values. In our case just `currentUserId`.
3. replace the values in the `RelationTuple` and evaluate the tuple against Keto
4. Allow / Deny access according to the result of the check.

For step 1 create another method inside `guarded-by.decorator.ts` [(full source)](https://github.com/nidomiro/relation-tuple-parser/blob/17a49ee97bc5075dfef67b94a13bc7f014f4dfd9/packages/examples/nestjs-guard/src/app/guard/guarded-by.decorator.ts#L25) with the following definition:

```ts
export const getGuardingRelationTuple = (
	reflector: Reflector,
	handler: Parameters<Reflector['get']>[1],
): RelationTupleWithReplacements<PossibleReplacements> | null => {
	return (
		reflector.get<GuardedByMetadataType, typeof GUARDED_BY_METADATA_KEY>(GUARDED_BY_METADATA_KEY, handler) ?? null
	)
}
```

Now we can retrieve the `RelationTupleWithReplacements` if any is set. Otherwise, we get null instead.

For step 2 I chose the "simple demo approach" and just extract a `userId` from the URL parameters. **Only** use this approach for demos, **never** for production! [(source code)](https://github.com/nidomiro/relation-tuple-parser/blob/main/packages/examples/nestjs-guard/src/app/guard/keto-guard.ts#L50)

Step 3 is implemented in `keto-read-client.service.ts` [(full source)](https://github.com/nidomiro/relation-tuple-parser/blob/main/packages/examples/nestjs-guard/src/app/keto-read-client.service.ts#L19).
With `RelationTupleWithReplacementsConverter.toKetoGrpcCheckRequest` we replace the values inside the RelationTuple and store the result directly in an `CheckRequest`, needed by Keto's gRPC client.
The next step is to call the Keto client with our `CheckRequest` and return the result. Note that the error-status `NOT_FOUND` is handled as "not allowed" [(see here)](https://github.com/nidomiro/relation-tuple-parser/blob/main/packages/examples/nestjs-guard/src/app/keto-read-client.service.ts#L29).

The last and final step is to use the Result and allow or deny the Request.

Now we need to load some rules into keto and check the result.
For this demo the necessary rules are set in keto during app-startup [(full-source)](https://github.com/nidomiro/relation-tuple-parser/blob/main/packages/examples/nestjs-guard/src/main.ts#L13):
```ts
async function initKeto(ketoWriteService: KetoWriteClientService) {
	await ketoWriteService.addRelationTuple(parseRelationTuple('groups:users#member@user1').unwrapOrThrow())
	await ketoWriteService.addRelationTuple(parseRelationTuple('groups:users#member@user2').unwrapOrThrow())
}
```
With these Relation Tuples set, you can now check the result yourself:
* Allowed access: http://localhost:3333/api?userId=user1
* Allowed access: http://localhost:3333/api?userId=user2
* Forbidden access: http://localhost:3333/api?userId=user3
* Forbidden access: http://localhost:3333/api

Allowed Access returns:
```
Access grated!
```

Denied Access returns:
```
{"statusCode":403,"message":"Forbidden resource","error":"Forbidden"}
```

And that's it. Now you can add more Controller and/or Handler and just use the `@GuardedBy` decorator to secure them.

In a real world-example you would also validate the session and fetch the data of the `currentUserId` inside the guard. The fetched data can then be attached to the request and therefore be used by the Handler without the need to fetch the data again.

If you have any questions or improvements feel free to comment below.