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

Tenant Token #89

Merged
merged 40 commits into from
Mar 9, 2022
Merged
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
57632e7
init specification
gmourier Nov 3, 2021
dbfa335
update filename
gmourier Nov 3, 2021
53844f4
update typo
gmourier Nov 3, 2021
cf70826
rephrase motivation
gmourier Nov 3, 2021
c229ad5
rename master occurences by main
gmourier Nov 9, 2021
f8efda5
replace mention of main by master
gmourier Nov 24, 2021
356ceeb
Update text/0089-scoped-api-keys.md
gmourier Nov 25, 2021
188ed2e
replace client code by frontend or backend
gmourier Nov 25, 2021
dcc555a
Update text/0089-scoped-api-keys.md
gmourier Nov 26, 2021
43ab009
Update text/0089-scoped-api-keys.md
gmourier Nov 26, 2021
0ddfa01
Update text/0089-scoped-api-keys.md
gmourier Nov 26, 2021
d5a9e25
update javascript code sample for generateScopedApiKey method
gmourier Nov 25, 2021
ed36888
Rename Scoped API Key to Tenant Token
gmourier Dec 13, 2021
ef094e6
Apply suggestions from code review
gmourier Dec 13, 2021
ace7dce
precise message from reviews
gmourier Jan 3, 2022
1db05d8
Add JWT part
gmourier Jan 13, 2022
2a93236
Rename specification file
gmourier Jan 14, 2022
d8da26d
Update specification texts
gmourier Jan 14, 2022
aa77a67
Add examples for indexesPolicy
gmourier Jan 14, 2022
9502867
Update indexesPolicy examples texts
gmourier Jan 14, 2022
1d99c31
Update indexesPolicy examples texts
gmourier Jan 14, 2022
dfe2882
Update indexesPolicy examples texts
gmourier Jan 14, 2022
4a88c3a
Add a multi-tenant definition and tenant examples for MeiliSearch
gmourier Jan 14, 2022
d955b54
Update text/0089-tenant-tokens.md
gmourier Jan 18, 2022
2669521
Update text/0089-tenant-tokens.md
gmourier Jan 18, 2022
bc52bdd
Add array format for indexesPolicy and rename iss to apiKeyPrefix
gmourier Jan 24, 2022
e2c1523
update indexesPolicy formats example
gmourier Jan 24, 2022
247996f
rename indexesPolicy to searchRules and add supported JWT signatures
gmourier Jan 24, 2022
87c3799
Rephrase searchRules explanations
gmourier Jan 24, 2022
94a754f
Update text/0089-tenant-tokens.md
gmourier Jan 24, 2022
624bf68
Update text/0089-tenant-tokens.md
gmourier Jan 24, 2022
2bcd81f
Update text/0089-tenant-tokens.md
gmourier Jan 24, 2022
f40e687
Rephrase explanations from suggestions
gmourier Jan 24, 2022
d7ff25a
Update scheme
gmourier Jan 25, 2022
68f7767
Update text/0089-tenant-tokens.md
gmourier Jan 25, 2022
8e9edae
Mention tenant token revoking
gmourier Jan 26, 2022
5f0ff4e
Add precision on SDKs and Meilisearch role for Tenant Token
gmourier Feb 16, 2022
56d3aaa
Apply suggestions from code review
gmourier Feb 20, 2022
1f3a26a
Add Future Possibilities on tenant token formatting error
gmourier Feb 22, 2022
4bc0396
Replace MeiliSearch by Meilisearch, fix typos, rephrase sentences and…
gmourier Mar 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
gmourier marked this conversation as resolved.
Show resolved Hide resolved
{
"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": {
gmourier marked this conversation as resolved.
Show resolved Hide resolved
"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.