Skip to content

Commit

Permalink
break up multitenancy page
Browse files Browse the repository at this point in the history
  • Loading branch information
guimachiavelli committed May 23, 2024
1 parent 1bb6040 commit af71faf
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 271 deletions.
19 changes: 17 additions & 2 deletions config/sidebar-learn.json
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,16 @@
"label": "Securing your project",
"slug": "basic_security"
},
{
"source": "learn/security/generate_tenant_token_sdk.mdx",
"label": "Generate tenant tokens with an official SDK",
"slug": "generate_tenant_token_sdk"
},
{
"source": "learn/security/generate_tenant_token_third_party.mdx",
"label": "Generate tenant tokens with a third party tool",
"slug": "generate_tenant_token_third_party"
},
{
"source": "learn/security/managing_api_keys.mdx",
"label": "Managing API keys",
Expand All @@ -241,6 +251,11 @@
"label": "Resetting the master key",
"slug": "resetting_master_key"
},
{
"source": "learn/security/tenant_token_reference.mdx",
"label": "Tenant token payload reference",
"slug": "tenant_token_reference"
},
{
"source": "learn/security/differences_master_api_keys.mdx",
"label": "Differences between the master key and API keys",
Expand All @@ -252,9 +267,9 @@
"slug": "protected_unprotected"
},
{
"source": "learn/security/tenant_tokens.mdx",
"source": "learn/security/multitenancy_tenant_tokens.mdx",
"label": "Multitenancy and tenant tokens",
"slug": "tenant_tokens"
"slug": "multitenancy_tenant_tokens"
}
]
},
Expand Down
28 changes: 28 additions & 0 deletions learn/security/generate_tenant_token_sdk.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
title: Multitenancy and tenant tokens — Meilisearch documentation
description: This guide shows you the main steps when creating tenant tokens using Meilisearch's official SDKs.
---

# Generating tenant tokens with an official SDK

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:

<CodeSamples id="tenant_token_guide_generate_sdk_1" />

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.](#search-rules)

As its name indicates, **API key** must be a valid Meilisearch API key with access to [the search action](/reference/api/keys#actions). 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](#tenant-token-payload-reference).

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

<CodeSamples id="tenant_token_guide_search_sdk_1" />

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.
95 changes: 95 additions & 0 deletions learn/security/generate_tenant_token_third_party.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
title: Multitenancy and tenant tokens — Meilisearch documentation
description: Use tenant tokens to manage complex applications with many users handling sensitive data.
---

# Generate tenant tokens without a Meilisearch SDK

Tenant tokens follow the [JWT standard](https://jwt.io). This means you can use a number of [compatible third-party libraries](https://jwt.io/libraries) to generate them. You may also skip all assistance and generate a token from scratch.

## Generate a tenant token with `node-jsonwebtoken`

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`](https://github.com/auth0/node-jsonwebtoken) library:

```js
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](#tenant-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.

<CodeSamples id="tenant_token_guide_search_no_sdk_1" />

<Capsule intent="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.
</Capsule>

## Generate a tenant token from scratch

Generating tenant tokens without a library is possible, but requires more effort for little gain.

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

```json
{
"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](#tenant-token-payload-reference) for more information on the requirements for each payload field.

```json
{
"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:

<CodeSamples id="tenant_token_guide_search_no_sdk_1" />

<Capsule intent="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.
</Capsule>
30 changes: 30 additions & 0 deletions learn/security/multitenancy_tenant_tokens.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
title: Multitenancy and tenant tokens — Meilisearch documentation
description: In this article you'll read what multitenancy is and how tenant tokens help managing complex applications and sensitive data.
---

# Multitenancy and tenant tokens

In this article you'll read what multitenancy is and how tenant tokens help managing complex applications and sensitive data.

## What is multitenancy?

In software development, multitenancy means that multiple users or 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](https://www.digitalocean.com/products/droplets) and [Amazon's AWS](https://aws.amazon.com/).

If your Meilisearch application stores sensitive data belonging to multiple users in the same index, 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.** They are meant to be short-lived, so Meilisearch does not store nor keep track of generated tokens.

Tenant tokens do not require any specific Meilisearch configuration. Instead, your application must have a system for token generation in place. The quickest method to generate tenant tokens is [using an official SDK](/learn/security/generate_tenant_token_sdk). It is also possible to [generate a token with a third-party library](/learn/security/generate_tenant_token_third_party).

## Sample application

Meilisearch developed an in-app search demo using multi-tenancy in a SaaS CRM. It only allows authenticated users to search through contacts, companies, and deals belonging to their organization.

Check out this [sample application](https://saas.meilisearch.com/?utm_campaign=oss&utm_source=docs&utm_medium=tenant-tokens). Its code is publicly available in a dedicated [GitHub repository](https://github.com/meilisearch/saas-demo/).

<Capsule intent="tip">
You can also use tenant tokens in role-based access control (RBAC) systems. Consult [How to implement RBAC with Meilisearch](https://blog.meilisearch.com/role-based-access-guide/) on Meilisearch's official blog for more information.
</Capsule>
119 changes: 119 additions & 0 deletions learn/security/tenant_token_reference.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
title: Multitenancy and tenant tokens — Meilisearch documentation
description: Use tenant tokens to manage complex applications with many users handling sensitive data.
---

# Tenant token payload reference

Meilisearch's tenant tokens are JWTs. Their payload is made of three elements: [search rules](#search-rules), an [API key](#api-key), and an optional [expiration date](#expiry-date).

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

<CodeSamples id="tenant_token_guide_generate_sdk_1" />

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

```json
{
"[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](/reference/api/search#filter)**.

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

```json
{
"*": {
"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`:

```json
{
"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:

```json
{
"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:

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

<Capsule intent="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](/reference/api/search#filter).
</Capsule>

## API key

Creating a token requires an API key with access to [the search action](/reference/api/keys#actions). 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.

<Capsule intent="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.
</Capsule>

[You can read more about API keys in our dedicated guide.](/learn/security/managing_api_keys)

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

<Capsule intent="danger">
The only way to revoke a token without an expiry date is to [delete](/reference/api/keys#delete-a-key) 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.
</Capsule>

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

0 comments on commit af71faf

Please sign in to comment.