• 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

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

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

The secret key that is used to encrypt the JWT token must be the original API Key value (not the UID).

# 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
apiKeyUid (Custom claim) Required Must contain the uid field value 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. apiKeyUid field

apiKeyUid permits to verify that the signing API key of the Token is known and valid within Meilisearch. It must contain the uid field value of the Meilisearch API key that generates and signs the Tenant Token.

The apiKeyUid can't be generated from the master key, and the API Key must have the search action defined to generate a usable tenant token.

# 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.

{
    "searchRules": {
        "*": {}
    }
}

is equivalent to

{
    "searchRules": {
        "*": null
    }
}

is equivalent to

{
    "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.

{
    "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.

{
    "searchRules": {
        "medical_records": {}
    }
}

is equivalent to

{
    "searchRules": {
        "medical_records": null
    }
}

is equivalent to

{
    "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.

{
    "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.

{
    "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.

{
    "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 Search Endpoints Specification.

# 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

{
    "apiKeyUid": "f0ec9882-0184-4303-89f0-d4c4d6912bcf", // The uid field value 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.