Skip to content
This repository has been archived by the owner on Mar 21, 2024. It is now read-only.

Commit

Permalink
Tenant Token (#89)
Browse files Browse the repository at this point in the history
* init specification

* update filename

* update typo

* rephrase motivation

* rename master occurences by main

* replace mention of main by master

* Update text/0089-scoped-api-keys.md

Co-authored-by: cvermand <33010418+bidoubiwa@users.noreply.github.com>

* replace client code by frontend or backend

* Update text/0089-scoped-api-keys.md

Co-authored-by: cvermand <33010418+bidoubiwa@users.noreply.github.com>

* Update text/0089-scoped-api-keys.md

Co-authored-by: cvermand <33010418+bidoubiwa@users.noreply.github.com>

* Update text/0089-scoped-api-keys.md

Co-authored-by: cvermand <33010418+bidoubiwa@users.noreply.github.com>

* update javascript code sample for generateScopedApiKey method

* Rename Scoped API Key to Tenant Token

* Apply suggestions from code review

Co-authored-by: cvermand <33010418+bidoubiwa@users.noreply.github.com>

* precise message from reviews

* Add JWT part

* Rename specification file

* Update specification texts

* Add examples for indexesPolicy

* Update indexesPolicy examples texts

* Update indexesPolicy examples texts

* Update indexesPolicy examples texts

* Add a multi-tenant definition and tenant examples for MeiliSearch

* Update text/0089-tenant-tokens.md

Co-authored-by: Tommy <68053732+dichotommy@users.noreply.github.com>

* Update text/0089-tenant-tokens.md

Co-authored-by: Tommy <68053732+dichotommy@users.noreply.github.com>

* Add array format for indexesPolicy and rename iss to apiKeyPrefix

* update indexesPolicy formats example

* rename indexesPolicy to searchRules and add supported JWT signatures

* Rephrase searchRules explanations

* Update text/0089-tenant-tokens.md

Co-authored-by: cvermand <33010418+bidoubiwa@users.noreply.github.com>

* Update text/0089-tenant-tokens.md

Co-authored-by: cvermand <33010418+bidoubiwa@users.noreply.github.com>

* Update text/0089-tenant-tokens.md

Co-authored-by: cvermand <33010418+bidoubiwa@users.noreply.github.com>

* Rephrase explanations from suggestions

* Update scheme

* Update text/0089-tenant-tokens.md

Co-authored-by: Many <legendre.maxime.isn@gmail.com>

* Mention tenant token revoking

* Add precision on SDKs and Meilisearch role for Tenant Token

* Apply suggestions from code review

Co-authored-by: Bruno Casali <brunoocasali@gmail.com>
Co-authored-by: cvermand <33010418+bidoubiwa@users.noreply.github.com>

* Add Future Possibilities on tenant token formatting error

* Replace MeiliSearch by Meilisearch, fix typos, rephrase sentences and reorganize sections

Co-authored-by: cvermand <33010418+bidoubiwa@users.noreply.github.com>
Co-authored-by: Tommy <68053732+dichotommy@users.noreply.github.com>
Co-authored-by: Many <legendre.maxime.isn@gmail.com>
Co-authored-by: Bruno Casali <brunoocasali@gmail.com>
  • Loading branch information
5 people authored Mar 9, 2022
1 parent 81d6a25 commit c4fa372
Showing 1 changed file with 268 additions and 0 deletions.
268 changes: 268 additions & 0 deletions text/0089-tenant-tokens.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
- Title: Tenant Tokens

# Tenant Tokens

## 1. Summary

A `Tenant token` is generated by the user code to be used by an end-user when making search queries.

It allows users to have multi-tenant indexes and thus restricts access to documents depending on the end-user making the search request.

A Tenant Token is a JWT containing the information necessary for Meilisearch to verify it and extract permission/rules to apply it to the end user's search.

### 1.1. Key Points

- `Tenant tokens` are JWTs generated on the user side by using Meilisearch SDKs or their custom code. `Tenant tokens` are not stored nor retrievable on the Meilisearch side.
- `Tenant tokens` contain rules that ensure that a `Tenant token` holder (e.g. an end-user) only has access to documents matching rules chosen at the `tenant token` creation.
- `Tenant tokens` are signed from a Meilisearch `API key` resource on the user's code.
- `Tenant tokens` must not be signed by the master key.
- `Tenant tokens` cannot be more permissive than the signing `API key`.
- `Tenant tokens` must be signed by an `API Key` having the `search` action defined.
- `Tenant tokens` can have different rules for each index accessible by the signing API key. These rules are described in the `searchRules` JSON object.
- The only rule available in the `searchRules` object is the search parameter `filter`.
- `Tenant tokens` are sent to Meilisearch via the `Authorization` header like any `API Keys` or the master key.
- When Meilisearch receives a search query emitted with a `Tenant token`, the `tenant token` is decoded, then the `searchRules` are applied for the search request before the search parameters.

## 2. Motivation

`Tenant tokens` are introduced to solve multi-tenant indexes use-case.

> Multi-Tenant Indexes Definition: It is an index that stores documents that may belong to different tenants. In our case, a tenant within an index can be a user or a company, etc. In general, the data of one tenant should not be accessible by other tenants.
Users today need to set up workarounds to have multi-tenant indexes. They have to use server code to implement the access restriction logic before requesting Meilisearch. It isn't easy to maintain, to implement, and the performance is not optimal because the frontend code does not communicate directly with Meilisearch.

## 3. Functional Specification

### 3.1. Example: Solving Multi-Tenancy with `Tenant tokens`

![](https://user-images.githubusercontent.com/3692335/151013496-d33ab507-f972-465d-b942-899fc2bd0a22.png)

`Mark` is a developer for a SaaS platform. He would like to ensure that every end-user can only access their documents at search time.

When an end-user registers, Mark's backend code generates a `Tenant token` for that end-user so they can only access their documents at search time.

This tenant-token is signed with a Meilisearch API Key so that Meilisearch can ensure that the tenant-token has been generated from a known entity.

Meilisearch checks if the Tenant Token is authorized to make the search request.

Then Meilisearch extracts the Tenant Token's rules to apply for the search request.

### 3.2. `Tenant Token` Details

Tenant Tokens are JWTs and must respect several conditions to be understandable by a Meilisearch instance.

#### 3.2.1. Header: Algorithm and token type

The Tenant Token must be signed with one of the following algorithms:

- `HS256`
- `HS384`
- `HS512`

e.g. With `HS256`

```json
{
"alg": "HS256",
"typ": "JWT"
}
```

#### 3.2.2. Payload: Data

Meilisearch needs information within the tenant token to check its validity and use it to authorize and perform end-user search requests.

##### 3.2.2.1. Validity Information

| Fields | Required | Description | Comments |
| -------- |----------- | ----------- | -------- |
| `apiKeyPrefix` (Custom claim) | Required | Must contain the first 8 characters of the signing `Meilisearch API key` used to generate the JWT | |
| `exp` (Expiration Time claim) | Optional | A JSON numeric value representing the number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time. | If the signing API key expires, the Tenant Token also expires. Thus said, the `exp` can't be greater than the expiration date of the signing API key. |

###### 3.2.2.1.1. `apiKeyPrefix` field

`apiKeyPrefix` permits to verify that the signing API key of the Token is known and valid within Meilisearch. It must contain the first 8 characters of the Meilisearch API key that generates and signs the Tenant Token.

The `apiKeyPrefix` can't be generated from a master key, and the `API Key` must have the `search` action defined.

###### 3.2.2.1.2. `exp` field

`exp` is used to specify the expiration date of the Tenant Token if needed. The format is a JSON numeric value representing the number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time, ignoring leap seconds.

##### 3.2.2.2. Business Logic Information

| Fields | Required | Description | Comments |
| -------- | --------- |------------ | -------- |
| `searchRules` | Required | This JSON object contains rules to apply for search queries performed with the JWT depending on the searched index. A Tenant Token cannot access more indexes at search time than those defined as accessible by the signing API key. | Let's say an index uses a field to separate documents belonging to one end-user from another one, but another index needs to separate belonging using a different field in its schema. Defining specific search rules per accessible index avoids generating several tenant tokens for an end-user. |

###### 3.2.2.2.1. `searchRules` JSON object

`searchRules` contains the rules to be enforced at search time for all or specific accessible indexes for the signing API Key.

Here are the accepted formats for the `searchRules` property.

---

> In this case, all indexes on which the signing API Key has permissions are searchable by the tenant token without any restrictions.
```json
{
"searchRules": {
"*": {}
}
}
```

is equivalent to

```json
{
"searchRules": {
"*": null
}
}
```

is equivalent to

```json
{
"searchRules": ["*"]
}
```

The search is authorized on all accessible indexes from the signing API Key for the Tenant Token without specific rules.

---

> In this case, all searchable indexes from the signing API Key are searchable by the tenant token, and Meilisearch applies the `filter` search rule before applying the request search parameters.
```json
{
"searchRules": {
"*": {
"filter": "user_id = 1"
}
}
}
```

---

> In this case, if the `medical_records` index is searchable from the signing API Key, the tenant token is only authorized to search in the `medical_records` index.
```json
{
"searchRules": {
"medical_records": {}
}
}
```
is equivalent to

```json
{
"searchRules": {
"medical_records": null
}
}
```

is equivalent to

```json
{
"searchRules": ["medical_records"]
}
```

---

> In this case, if the `medical_records` index is searchable from the signing API Key, the tenant token is only authorized to search in the `medical_records` index, and Meilisearch applies the `filter` search rule before applying the request search parameters.
```json
{
"searchRules": {
"medical_records": {
"filter": "user_id = 1"
}
}
}
```

---

> In this case, if the `medical_records` and `medical_appointments` indexes are searchable from the signing API Key, the tenant token is only authorized to search in those indexes, and Meilisearch applies the `filter` search rule before applying the request search parameters.
```json
{
"searchRules": {
"medical_records": {
"filter": "user_id = 1"
},
"medical_appointments": {
"filter": "user_id = 1 AND accepted = true"
}
}
}
```

---

> In this case, all searchable indexes from the signing API Key are searchable, and Meilisearch applies the `filter` search rule before applying the request search parameters for all indexes except for the `medical_appointments` index. A dedicated `filter` search rule is applied when making a search query on this index.
```json
{
"searchRules": {
"*": {
"filter": "user_id = 1"
},
"medical_appointments": {
"filter": "user_id = 1 AND accepted = true"
}
}
}
```
---

> The `filter` field accepts an array, a string, and the mixed syntax as described in the [filter and facet specification](0027-filter-and-facet-behavior.md).
##### 3.2.2.3. Payload example

Given a Meilisearch API Key used to sign the JWT from the user code. Here is an example of a valid payload for a tenant token.

e.g. `Meilisearch API key: rkDxFUHd02193e120218f72cc51a9db62729fdb4003e271f960d1631b54f3426fa8b2595`

```jsonc
{
"apiKeyPrefix": "rkDxFUHd", // The first 8 characters of the signing Meilisearch API Key
"exp": 1641835850, // An expiration date in seconds from 1970-01-01T00:00:00Z UTC
"searchRules": { // The searchRules Json Object definition
"*": {
"filter": "user_id = 1"
}
}
}
```

> In this example, `"*"` allows to specify that no matter which index is searched (among all those accessible by the signing API key that generated the tenant token), the `filter` search rule is applied on all search requests.

### 3.3. Tenant Token Revokation

It is not possible to revoke a specific tenant token.

The only way to do so is to **delete the API key that signed it** using the `DELETE - /keys/:apiKey` endpoints of Meilisearch.

🚨 **Doing this revoke all tenant tokens signed by this API Key.**

Another much more drastic method is to modify the `master key` of the Meilisearch instance.

🚨🚨 **Doing this regenerate all the API Keys and thus revoke all the tenant tokens generated regardless of the signing API Key.**

## 4. Future Possibilities

- Handle more signing methods for the Tenant Token.
- Handle more search parameters restrictions in `searchRules`.
- Add a possibility to revoke a specific Tenant Token.
- Introduce an endpoint to generate tenant tokens on the Meilisearch side.

0 comments on commit c4fa372

Please sign in to comment.