> ## Documentation Index
> Fetch the complete documentation index at: https://algolia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# User-restricted access to data

> You can use secured API keys to restrict the records users can retrieve.

export const Records = () => <Tooltip tip="A record is a searchable object in an Algolia index. Each record consists of named attributes." cta="Algolia records" href="/doc/guides/sending-and-managing-data/prepare-your-data#algolia-records">
    records
  </Tooltip>;

export const Index = () => <Tooltip tip="An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.">
    index
  </Tooltip>;

Sometimes, you don't want your users to search your entire <Index />, but only a subset that concerns them.
You can restrict content to a specific user, a set of users, a group, or everyone.
Handling access within an index allows to have a fine-grained control over who can search and view what content.

**This doesn't mean you need one index per user.**
By generating a secured API key for the current user, you can restrict the <Records /> they can retrieve.

## Add an attribute for filtering in your dataset

Algolia is schemaless and doesn't have any concept of relationships between objects, so you need to put all the relevant information in each record.

Take a dataset for corporate documents as an example. The index contains the entire list of documents for the company, but has dedicated access control to restrict who can view content.

Consider different users in this company: Angela, Mike, and Ruth. Angela is an executive, Mike the accountant, and Ruth is an engineer.

```json JSON icon=braces theme={"system"}
[
  {
    "title": "Financial record Q3 and pipeline forecast",
    "visible_by": ["Angela", "group/Finance", "group/Shareholders"],
    "objectID": "myID1",
    "content": "..."
  },
  {
    "title": "Compliance audit check-list",
    "visible_by": ["group/Finance"],
    "objectID": "myID2",
    "content": "..."
  },
  {
    "title": "Strategic partnership with BigCompany",
    "visible_by": ["Angela"],
    "objectID": "myID3",
    "content": "..."
  },
  {
    "title": "New company-wide healthcare coverage benefits",
    "visible_by": ["group/Everybody"],
    "objectID": "myID4",
    "content": "..."
  },
  {
    "title": "Ruth's personal TODO list",
    "visible_by": ["Ruth"],
    "objectID": "myID5",
    "content": "..."
  }
]
```

Each record has a `visible_by` attribute, which has a list of users, or groups. Only listed users and groups can see the specific record, with the group Everybody visible by anyone. When searching through it, only allowed people should be able to find those records.

## Set up user-restricted access

To restrict access, configure an attribute for filtering, generate secured API keys with embedded filters, and optionally hide the attribute from API responses.

### Make the attribute filterable

To run the code examples on this page, [install the latest API client](/doc/libraries/sdk/install).

To make your `visible_by` attribute filterable, add it in [`attributesForFaceting`](/doc/api-reference/api-parameters/attributesForFaceting).

<CodeGroup>
  ```cs C# theme={"system"}
  var response = await client.SetSettingsAsync(
    "INDEX_NAME",
    new IndexSettings { AttributesForFaceting = new List<string> { "filterOnly(visible_by)" } }
  );
  ```

  ```dart Dart theme={"system"}
  final response = await client.setSettings(
    indexName: "INDEX_NAME",
    indexSettings: IndexSettings(
      attributesForFaceting: [
        "filterOnly(visible_by)",
      ],
    ),
  );
  ```

  ```go Go theme={"system"}
  response, err := client.SetSettings(client.NewApiSetSettingsRequest(
    "INDEX_NAME",
    search.NewEmptyIndexSettings().SetAttributesForFaceting(
      []string{"filterOnly(visible_by)"})))
  if err != nil {
    // handle the eventual error
    panic(err)
  }
  ```

  ```java Java theme={"system"}
  UpdatedAtResponse response = client.setSettings(
    "INDEX_NAME",
    new IndexSettings().setAttributesForFaceting(Arrays.asList("filterOnly(visible_by)"))
  );
  ```

  ```js JavaScript theme={"system"}
  const response = await client.setSettings({
    indexName: 'INDEX_NAME',
    indexSettings: { attributesForFaceting: ['filterOnly(visible_by)'] },
  });
  ```

  ```kotlin Kotlin theme={"system"}
  var response =
    client.setSettings(
      indexName = "INDEX_NAME",
      indexSettings = IndexSettings(attributesForFaceting = listOf("filterOnly(visible_by)")),
    )
  ```

  ```php PHP theme={"system"}
  $response = $client->setSettings(
      'INDEX_NAME',
      ['attributesForFaceting' => [
          'filterOnly(visible_by)',
      ],
      ],
  );
  ```

  ```python Python theme={"system"}
  response = client.set_settings(
      index_name="INDEX_NAME",
      index_settings={
          "attributesForFaceting": [
              "filterOnly(visible_by)",
          ],
      },
  )
  ```

  ```ruby Ruby theme={"system"}
  response = client.set_settings(
    "INDEX_NAME",
    Algolia::Search::IndexSettings.new(attributes_for_faceting: ["filterOnly(visible_by)"])
  )
  ```

  ```scala Scala theme={"system"}
  val response = Await.result(
    client.setSettings(
      indexName = "INDEX_NAME",
      indexSettings = IndexSettings(
        attributesForFaceting = Some(Seq("filterOnly(visible_by)"))
      )
    ),
    Duration(100, "sec")
  )
  ```

  ```swift Swift theme={"system"}
  let response = try await client.setSettings(
      indexName: "INDEX_NAME",
      indexSettings: IndexSettings(attributesForFaceting: ["filterOnly(visible_by)"])
  )
  ```
</CodeGroup>

In this case, you only want to filter on this attribute, and not use it for facet counts.
To do this, add the [`filterOnly`](/doc/api-reference/api-parameters/attributesForFaceting#param-filter-only) modifier.
This improves performance because the engine doesn't have to compute the count for each value.

If you need faceting on this attribute, you can remove the [`filterOnly`](/doc/api-reference/api-parameters/attributesForFaceting#param-filter-only) modifier.

### Add and remove users

Whenever someone needs to change the access rights of a record, you need to update the `visible_by` attribute.

<CodeGroup>
  ```cs C# theme={"system"}
  var response = await client.PartialUpdateObjectAsync(
    "INDEX_NAME",
    "OBJECT_ID",
    new Dictionary<string, List<string>>
    {
      {
        "visible_by",
        new List<string> { "Angela", "group/Finance", "group/Shareholders" }
      },
    }
  );
  ```

  ```dart Dart theme={"system"}
  final response = await client.partialUpdateObject(
    indexName: "INDEX_NAME",
    objectID: "OBJECT_ID",
    attributesToUpdate: {
      'visible_by': [
        "Angela",
        "group/Finance",
        "group/Shareholders",
      ],
    },
  );
  ```

  ```go Go theme={"system"}
  response, err := client.PartialUpdateObject(client.NewApiPartialUpdateObjectRequest(
    "INDEX_NAME", "OBJECT_ID", map[string]any{"visible_by": []string{"Angela", "group/Finance", "group/Shareholders"}}))
  if err != nil {
    // handle the eventual error
    panic(err)
  }
  ```

  ```java Java theme={"system"}
  UpdatedAtWithObjectIdResponse response = client.partialUpdateObject(
    "INDEX_NAME",
    "OBJECT_ID",
    new HashMap() {
      {
        put("visible_by", Arrays.asList("Angela", "group/Finance", "group/Shareholders"));
      }
    }
  );
  ```

  ```js JavaScript theme={"system"}
  const response = await client.partialUpdateObject({
    indexName: 'theIndexName',
    objectID: 'OBJECT_ID',
    attributesToUpdate: { visible_by: ['Angela', 'group/Finance', 'group/Shareholders'] },
  });
  ```

  ```kotlin Kotlin theme={"system"}
  var response =
    client.partialUpdateObject(
      indexName = "INDEX_NAME",
      objectID = "OBJECT_ID",
      attributesToUpdate =
        buildJsonObject {
          put(
            "visible_by",
            JsonArray(
              listOf(
                JsonPrimitive("Angela"),
                JsonPrimitive("group/Finance"),
                JsonPrimitive("group/Shareholders"),
              )
            ),
          )
        },
    )
  ```

  ```php PHP theme={"system"}
  $response = $client->partialUpdateObject(
      'INDEX_NAME',
      'OBJECT_ID',
      ['visible_by' => [
          'Angela',

          'group/Finance',

          'group/Shareholders',
      ],
      ],
  );
  ```

  ```python Python theme={"system"}
  response = client.partial_update_object(
      index_name="INDEX_NAME",
      object_id="OBJECT_ID",
      attributes_to_update={
          "visible_by": [
              "Angela",
              "group/Finance",
              "group/Shareholders",
          ],
      },
  )
  ```

  ```ruby Ruby theme={"system"}
  response = client.partial_update_object(
    "INDEX_NAME",
    "OBJECT_ID",
    {visible_by: ["Angela", "group/Finance", "group/Shareholders"]}
  )
  ```

  ```scala Scala theme={"system"}
  val response = Await.result(
    client.partialUpdateObject(
      indexName = "INDEX_NAME",
      objectID = "OBJECT_ID",
      attributesToUpdate = JObject(
        List(
          JField(
            "visible_by",
            JArray(List(JString("Angela"), JString("group/Finance"), JString("group/Shareholders")))
          )
        )
      )
    ),
    Duration(100, "sec")
  )
  ```

  ```swift Swift theme={"system"}
  let response = try await client.partialUpdateObject(
      indexName: "INDEX_NAME",
      objectID: "OBJECT_ID",
      attributesToUpdate: ["visible_by": ["Angela", "group/Finance", "group/Shareholders"]]
  )
  ```
</CodeGroup>

The [`partialUpdateObjects`](/doc/libraries/sdk/v1/methods/partial-update-objects) lets you partially update an attribute instead of replacing the entire record, or even the entire index.

### Generate a secured API key

Frontend search can be vulnerable to malicious users who can tweak the request to impersonate another user and see content they shouldn't have access to.

To prevent this, generate a [secured API key](/doc/guides/security/api-keys#secured-api-keys) on the backend with filters (users can't alter these filters).

<CodeGroup>
  ```cs C# theme={"system"}
  var response = client.GenerateSecuredApiKey(
    "2640659426d5107b6e47d75db9cbaef8",
    new SecuredApiKeyRestrictions { Filters = "visible_by:group/Finance" }
  );
  ```

  ```go Go theme={"system"}
  response, err := client.GenerateSecuredApiKey(
    "2640659426d5107b6e47d75db9cbaef8",
    search.NewEmptySecuredApiKeyRestrictions().SetFilters("visible_by:group/Finance"))
  if err != nil {
    // handle the eventual error
    panic(err)
  }
  ```

  ```java Java theme={"system"}
  String response = client.generateSecuredApiKey(
    "2640659426d5107b6e47d75db9cbaef8",
    new SecuredApiKeyRestrictions().setFilters("visible_by:group/Finance")
  );
  ```

  ```js JavaScript theme={"system"}
  const response = client.generateSecuredApiKey({
    parentApiKey: '2640659426d5107b6e47d75db9cbaef8',
    restrictions: { filters: 'visible_by:group/Finance' },
  });
  ```

  ```kotlin Kotlin theme={"system"}
  var response =
    client.generateSecuredApiKey(
      parentApiKey = "2640659426d5107b6e47d75db9cbaef8",
      restrictions = SecuredApiKeyRestrictions(filters = "visible_by:group/Finance"),
    )
  ```

  ```php PHP theme={"system"}
  $response = $client->generateSecuredApiKey(
      '2640659426d5107b6e47d75db9cbaef8',
      ['filters' => 'visible_by:group/Finance',
      ],
  );
  ```

  ```python Python theme={"system"}
  response = client.generate_secured_api_key(
      parent_api_key="2640659426d5107b6e47d75db9cbaef8",
      restrictions={
          "filters": "visible_by:group/Finance",
      },
  )
  ```

  ```ruby Ruby theme={"system"}
  response = client.generate_secured_api_key(
    "2640659426d5107b6e47d75db9cbaef8",
    Algolia::Search::SecuredApiKeyRestrictions.new(filters: "visible_by:group/Finance")
  )
  ```

  ```scala Scala theme={"system"}
  val response = client.generateSecuredApiKey(
    parentApiKey = "2640659426d5107b6e47d75db9cbaef8",
    restrictions = SecuredApiKeyRestrictions(
      filters = Some("visible_by:group/Finance")
    )
  )
  ```

  ```swift Swift theme={"system"}
  let response = try client.generateSecuredApiKey(
      parentApiKey: "2640659426d5107b6e47d75db9cbaef8",
      restrictions: SecuredApiKeyRestrictions(filters: "visible_by:group/Finance")
  )
  ```
</CodeGroup>

You can **get this API key in your frontend** from your backend—for example, with an API request.

<Note>
  If a secured API key is compromised, you should invalidate the search API key used to generate it.
</Note>

### Make sensitive attributes inaccessible

When using a secured API key with an embedded filter, users can only retrieve content they're allowed to access to.
Since the API returns the `visible_by` attribute for each record, they can find out what other users have the same access for this record if they inspect the response.

To mitigate this privacy concern, use the [`unretrievableAttributes`](/doc/api-reference/api-parameters/unretrievableAttributes) parameter.
It ensures that the `visible_by` parameter is never part of the Algolia response, even though you use it for filtering on the engine side.

<CodeGroup>
  ```cs C# theme={"system"}
  var response = await client.SetSettingsAsync(
    "INDEX_NAME",
    new IndexSettings { UnretrievableAttributes = new List<string> { "total_number_of_sales" } }
  );
  ```

  ```dart Dart theme={"system"}
  final response = await client.setSettings(
    indexName: "INDEX_NAME",
    indexSettings: IndexSettings(
      unretrievableAttributes: [
        "total_number_of_sales",
      ],
    ),
  );
  ```

  ```go Go theme={"system"}
  response, err := client.SetSettings(client.NewApiSetSettingsRequest(
    "INDEX_NAME",
    search.NewEmptyIndexSettings().SetUnretrievableAttributes(
      []string{"total_number_of_sales"})))
  if err != nil {
    // handle the eventual error
    panic(err)
  }
  ```

  ```java Java theme={"system"}
  UpdatedAtResponse response = client.setSettings(
    "INDEX_NAME",
    new IndexSettings().setUnretrievableAttributes(Arrays.asList("total_number_of_sales"))
  );
  ```

  ```js JavaScript theme={"system"}
  const response = await client.setSettings({
    indexName: 'theIndexName',
    indexSettings: { unretrievableAttributes: ['total_number_of_sales'] },
  });
  ```

  ```kotlin Kotlin theme={"system"}
  var response =
    client.setSettings(
      indexName = "INDEX_NAME",
      indexSettings = IndexSettings(unretrievableAttributes = listOf("total_number_of_sales")),
    )
  ```

  ```php PHP theme={"system"}
  $response = $client->setSettings(
      'INDEX_NAME',
      ['unretrievableAttributes' => [
          'total_number_of_sales',
      ],
      ],
  );
  ```

  ```python Python theme={"system"}
  response = client.set_settings(
      index_name="INDEX_NAME",
      index_settings={
          "unretrievableAttributes": [
              "total_number_of_sales",
          ],
      },
  )
  ```

  ```ruby Ruby theme={"system"}
  response = client.set_settings(
    "INDEX_NAME",
    Algolia::Search::IndexSettings.new(unretrievable_attributes: ["total_number_of_sales"])
  )
  ```

  ```scala Scala theme={"system"}
  val response = Await.result(
    client.setSettings(
      indexName = "INDEX_NAME",
      indexSettings = IndexSettings(
        unretrievableAttributes = Some(Seq("total_number_of_sales"))
      )
    ),
    Duration(100, "sec")
  )
  ```

  ```swift Swift theme={"system"}
  let response = try await client.setSettings(
      indexName: "INDEX_NAME",
      indexSettings: IndexSettings(unretrievableAttributes: ["total_number_of_sales"])
  )
  ```
</CodeGroup>

### Search the subset

You can now [`search`](/doc/libraries/sdk/v1/methods/search) on the frontend using the API key generated from your backend.
This API key has an embedded filter on the `visible_by` attribute, so you have a guarantee that the current user only sees results that they're allowed to access.
