Multitenancy and tenant tokens

    In this guide you'll first learn what multitenancy is and how tenant tokens help managing complex applications and sensitive data. Then, you'll see how to generate and use tokens, whether with an official SDK or otherwise. The guide ends with a quick explanation of the main tenant token settings.

    Tenant tokens are generated by using API keys. This article may be easier to follow if you have already read the security tutorial and API keys guide.

    TIP

    You can also use tenant tokens in role-based access control (RBAC) systems. Consult How to implement RBAC with Meilisearch on Meilisearch's official blog for more information.

    What is multitenancy?

    In software development, multitenancy means that multiple users—also called tenants—share the same computing resources with different levels of access to system-wide data. Proper multitenancy is crucial in cloud computing services such as DigitalOcean's Droplets and Amazon's AWS.

    If your Meilisearch application stores sensitive data belonging to multiple users in the same index, we can say it is a multi-tenant index. In this context, it is very important to make sure users can only search through their own documents. This can be accomplished with tenant tokens.

    What are tenant tokens and how are they different from API keys in Meilisearch?

    Tenant tokens are small packages of encrypted data presenting proof a user can access a certain index. They contain not only security credentials, but also instructions on which documents within that index the user is allowed to see. Tenant tokens only give access to the search endpoints.

    To use tokens in Meilisearch, you only need to have a system for token generation in place. The quickest method to generate tenant tokens is using one of our official SDKs. It is also possible to generate a token from scratch.

    Tenant tokens do not require you to configure any specific instance options or index settings. They are also meant to be short-lived—Meilisearch does not store nor keeps track of generated tokens.

    Generating tenant tokens with an SDK

    Imagine that you are developing an application that allows patients and doctors to search through medical records. In your application, it is crucial that each patient can see only their own records and not those of another patient.

    The code in this example imports the SDK, creates a filter based on the current user's ID, and feeds that data into the SDK's generateTenantToken function. Once the token is generated, it is stored in the token variable:

    const searchRules = {
      patient_medical_records: {
        filter: 'user_id = 1'
      }
    }
    const apiKey = 'B5KdX2MY2jV6EXfUs6scSfmC...'
    const apiKeyUid = '85c3c2f9-bdd6-41f1-abd8-11fcf80e0f76'
    const expiresAt = new Date('2025-12-20') // optional
    
    const token = client.generateTenantToken(apiKeyUid, searchRules, {
      apiKey: apiKey,
      expiresAt: expiresAt,
    })

    There are three important parameters to keep in mind when using an SDK to generate a tenant token: search rules, API key, and expiration date. Together, they make the token's payload.

    Search rules must be a JSON object specifying the restrictions that will be applied to search requests on a given index. It must contain at least one search rule. To learn more about search rules, take a look at our tenant token payload reference.

    As its name indicates, API key must be a valid Meilisearch API key with access to the search action. A tenant token will have access to the same indexes as the API key used when generating it. If no API key is provided, the SDK might be able to infer it automatically.

    Expiration date is optional when using an SDK. Tokens become invalid after their expiration date. Tokens without an expiration date will expire when their parent API key does.

    You can read more about each element of a tenant token payload in this guide's final section.

    Using a tenant token with an SDK

    After creating a token, you can send it back to the front-end. There, you can use it to make queries that will only return results whose user_id attribute equals the current user's ID:

    const frontEndClient = new MeiliSearch({ host: 'http://localhost:7700', apiKey: token })
    frontEndClient.index('patient_medical_records').search('blood test')

    Applications may use tenant tokens and API keys interchangeably when searching. For example, the same application might use a default search API key for queries on public indexes and a tenant token for logged-in users searching on private data.

    Generating tenant tokens without a Meilisearch SDK

    Though Meilisearch recommends using an official SDK to generate tenant tokens, this is not a requirement. Since tenant tokens follow the JWT standard, you can use a number of compatible third-party libraries. You may also skip all assistance and generate a token from scratch, though this is probably unnecessary in most production environments.

    If you are already familiar with the creation of JWTs and only want to know about the specific requirements of a tenant token payload, skip this section and take a look at the token payload reference.

    Generating a tenant token with a third-party library

    Using a third-party library for tenant token generation is fairly similar to creating tokens with an official SDK. The following example uses the node-jsonwebtoken library:

    const jwt = require('jsonwebtoken');
    
    const apiKey = 'my_api_key';
    const apiKeyUid = 'ac5cd97d-5a4b-4226-a868-2d0eb6d197ab';
    const currentUserID = 'a_user_id';
    
    const tokenPayload = {
      searchRules: {
        'patient_medical_records': {
          'filter': `user_id = ${currentUserID}`
         }
      },
      apiKeyUid: apiKeyUid,
      exp: parseInt(Date.now() / 1000) + 20 * 60 // 20 minutes
    };
    
    const token = jwt.sign(tokenPayload, apiKey, {algorithm: 'HS256'});
    

    tokenPayload contains the token payload. It must contain three fields: searchRules, apiKeyUid, and exp.

    searchRules must be a JSON object containing a set of search rules. These rules specify restrictions applied to every query using this web token.

    apiKeyUid must be the uid of a valid Meilisearch API key.

    exp is the only optional parameter of a tenant token. It must be a UNIX timestamp specifying the expiration date of the token.

    For more information on each one of the tenant token fields, consult the token payload reference.

    tokenPayload is passed to node-jsonwebtoken's sign method, together with the complete API key used in the payload and the chosen encryption algorithm. Meilisearch supports the following encryption algorithms: HS256, HS384, and HS512.

    Though this example used node-jsonwebtoken, a NodeJS package, you may use any JWT-compatible library in whatever language you feel comfortable.

    After signing the token, you can use it to make search queries in the same way you would use an API key.

    curl \
      -X POST 'http://localhost:7700/indexes/patient_medical_records/search' \
      -H 'Authorization: Bearer TENANT_TOKEN'
    NOTE

    The curl example presented here is only for illustration purposes. In production environments, you would likely send the token to the front-end of your application and query indexes from there.

    Generating a tenant token from scratch

    Generating tenant tokens without a library is possible, but requires considerably more effort.

    Though creating a JWT from scratch is out of scope for this guide, here's a quick summary of the necessary steps.

    The full process requires you to create a token header, prepare the data payload with at least one set of search rules, and then sign the token with an API key.

    The token header must specify a JWT type and an encryption algorithm. Supported tenant token encryption algorithms are HS256, HS384, and HS512.

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

    The token payload contains most of the relevant token data. It must be an object containing a set of search rules and the first 8 characters of a Meilisearch API key. You may optionally set an expiration date for your token. Consult the token payload reference for more information on the requirements for each payload field.

    {
      "exp": 1646756934,
      "apiKeyUid": "ac5cd97d-5a4b-4226-a868-2d0eb6d197ab",
      "searchRules": {
        "patient_medical_records": {
          "filter": "user_id = 1"
        }
      }
    }
    

    You must then encode both the header and the payload into base64, concatenate them, and finally generate the token by signing it using your chosen encryption algorithm.

    Once your token is ready, it can seamlessly replace API keys to authorize requests made to the search endpoint:

    curl \
      -X POST 'http://localhost:7700/indexes/patient_medical_records/search' \
      -H 'Authorization: Bearer TENANT_TOKEN'
    NOTE

    The curl example presented here is only for illustration purposes. In production environments, you would likely send the token to the front-end of your application and query indexes from there.

    Tenant token payload reference

    Meilisearch's tenant tokens are JWTs. Their payload is made of three elements: search rules, an API key, and an optional expiration date.

    You can see each one of them assigned to its own variable in this example:

    const searchRules = {
      patient_medical_records: {
        filter: 'user_id = 1'
      }
    }
    const apiKey = 'B5KdX2MY2jV6EXfUs6scSfmC...'
    const apiKeyUid = '85c3c2f9-bdd6-41f1-abd8-11fcf80e0f76'
    const expiresAt = new Date('2025-12-20') // optional
    
    const token = client.generateTenantToken(apiKeyUid, searchRules, {
      apiKey: apiKey,
      expiresAt: expiresAt,
    })

    Search rules

    Search rules are a set of instructions defining search parameters that will be enforced in every query made with a specific tenant token.

    searchRules must contain a JSON object specifying rules that will be enforced on any queries using this token. Each rule is itself a JSON object and must follow this pattern:

    {
      "[index_name]": {
        "[search_parameter]": "[value]"
      }
    }
    

    The object key must be an index name. You may use the * wildcard instead of a specific index name—in this case, search rules will be applied to all indexes.

    The object value must consist of search_parameter:value pairs. Currently, tenant tokens only support the filter search parameter.

    In this example, all queries across all indexes will only return documents whose user_id equals 1:

    {
      "*": {
        "filter": "user_id = 1"
      }
    }
    

    You can also use the * wildcard by adding it at the end of a string. This allows the tenant token to access all index names starting with that string.

    The following example queries across all indexes starting with the string medical (like medical_records) and returns documents whose user_id equals 1:

    {
      "medical*": {
        "filter": "user_id = 1 AND published = true"
      }
    }
    

    The next rule goes a bit further. When searching on the patient_medical_records index, a user can only see records that belong to them and have been marked as published:

    {
      "patient_medical_records": {
        "filter": "user_id = 1 AND published = true"
      }
    }
    

    A token may contain rules for any number of indexes. Specific rulesets take precedence and overwrite * rules.

    The previous rules can be combined in one tenant token:

    {
      "apiKeyUid": "ac5cd97d-5a4b-4226-a868-2d0eb6d197ab",
      "exp": 1641835850,
      "searchRules": {
        "*": {
          "filter": "user_id = 1"
        },
        "medical_records": {
          "filter": "user_id = 1 AND published = true",
        }
      }
    }
    
    DANGER

    Because tenant tokens are generated in your application, Meilisearch cannot check if search rule filters are valid. Invalid search rules will only throw errors when they are used in a query.

    Consult the search API reference for more information on Meilisearch filter syntax.

    API key

    Creating a token requires an API key with access to the search action. A token has access to the same indexes and routes as the API key used to generate it.

    Since a master key is not an API key, you cannot use a master key to create a tenant token.

    For security reasons, we strongly recommend you avoid exposing the API key whenever possible and always generate tokens on your application's back-end.

    When using an official Meilisearch SDK, you may indicate which API key you wish to use when generating a token. Consult the documentation of the SDK you are using for more specific instructions.

    WARNING

    If an API key expires, any tenant tokens created with it will become invalid. The same applies if the API key is deleted or regenerated due to a changed master key.

    You can read more about API keys in our dedicated guide.

    Expiry date

    It is possible to define an expiry date when generating a token. This is good security practice and Meilisearch recommends setting relatively short token expiry dates whenever possible.

    The expiry date must be a UNIX timestamp or null. Additionally, a token's expiration date cannot exceed its parent API key's expiration date. For example, if an API key is set to expire on 2022-10-15, a token generated with that API key cannot be set to expire on 2022-10-16.

    Setting a token expiry date is optional, but recommended. A token without an expiry date never expires and can be used indefinitely as long as its parent API key remains valid.

    DANGER

    The only way to revoke a token without an expiry date is to delete its parent API key.

    Changing an instance's master key forces Meilisearch to regenerate all API keys and will also render all existing tenant tokens invalid.

    When using an official Meilisearch SDK, you may indicate the expiry date when generating a token. Consult the documentation of the SDK you are using for more specific instructions.

    Example application

    Our in-app search demo implements multi-tenancy in a SaaS (Software as a Service) CRM. It only allows authenticated users to search through contacts, companies, and deals belonging to their organization.

    Check out the SaaS demo and the GitHub repository.