|
| 1 | +# Scoped API Keys |
| 2 | + |
| 3 | +Typesense is designed with security and fine-grained access control in mind. To perform any action with Typesense, you need API keys. Typesense also allows access control on API keys. You can define capabilities as to what a user can or cannot do. You can also restrict access to a specific document or collection. In the case of a multi-tenant environment, you can scope API keys to a particular subset. This is helpful when you have indexed data from multiple tenants in your Typesense server and want to restrict users to only access their subset of data. |
| 4 | + |
| 5 | +Typesense allows you to create API keys that have pre-defined filters embedded in them. So, whenever you run a search query with these API keys, those filters are automatically applied and cannot be overridden. You can then provide those search API keys to users and they would only be able to access the data that is allowed by the set filter. To create scoped API keys, you just need a parent key. |
| 6 | + |
| 7 | +Let's take example of a [company collection](../../api/collections.html#create-a-collection), that has the following documents: |
| 8 | + |
| 9 | +```shell |
| 10 | +{"company_id":124,"company_name":"Stark Industries","country":"USA","id":"0","num_employees":3355} |
| 11 | +{"company_id":125,"company_name":"Wayne Enterprises","country":"USA","id":"1","num_employees":4538} |
| 12 | +{"company_id":126,"company_name":"Daily Planet","country":"USA","id":"2","num_employees":2232} |
| 13 | +{"company_id":127,"company_name":"New Stark Industries","country":"USA","id":"3","num_employees":7945} |
| 14 | +``` |
| 15 | + |
| 16 | +Now, let's create a scoped API key that will restrict access to documents that have the `company_id` value as 124. |
| 17 | + |
| 18 | +<Tabs :tabs="['JavaScript','PHP','Python','Ruby', 'Shell']"> |
| 19 | + <template v-slot:JavaScript> |
| 20 | + |
| 21 | +```javascript |
| 22 | +keyWithSearchPermissions = 'RN23GFr1s6jQ9kgSNg2O7fYcAUXU7127' |
| 23 | +client.keys().generateScopedSearchKey(keyWithSearchPermissions, {'filter_by': 'company_id:124', 'expires_at': 1611590465}) |
| 24 | +``` |
| 25 | + </template> |
| 26 | + |
| 27 | + <template v-slot:PHP> |
| 28 | + |
| 29 | +```php |
| 30 | +$keyWithSearchPermissions = 'RN23GFr1s6jQ9kgSNg2O7fYcAUXU7127'; |
| 31 | +$client->keys()->generateScopedSearchKey($keyWithSearchPermissions, ['filter_by' => 'company_id:124', 'expires_at' => 1611590465]); |
| 32 | +``` |
| 33 | + </template> |
| 34 | + <template v-slot:Python> |
| 35 | + |
| 36 | +```python |
| 37 | +key_with_search_permissions = 'RN23GFr1s6jQ9kgSNg2O7fYcAUXU7127' |
| 38 | +client.keys().generate_scoped_search_key(key_with_search_permissions, {"filter_by": "company_id:124", "expires_at": 1611590465}) |
| 39 | +``` |
| 40 | + </template> |
| 41 | + <template v-slot:Ruby> |
| 42 | + |
| 43 | +```ruby |
| 44 | +key_with_search_permissions = 'RN23GFr1s6jQ9kgSNg2O7fYcAUXU7127' |
| 45 | +client.keys().generate_scoped_search_key(key_with_search_permissions, {'filter_by': 'company_id:124', 'expires_at': 1611590465}) |
| 46 | +``` |
| 47 | + </template> |
| 48 | + <template v-slot:Shell> |
| 49 | + |
| 50 | +```bash |
| 51 | +KEY_WITH_SEARCH_PERMISSIONS="RN23GFr1s6jQ9kgSNg2O7fYcAUXU7127" |
| 52 | +EMBEDDED_SEARCH_PARAMETERS_JSON='{"filter_by":"company_id:124","expires_at":1611590465}' |
| 53 | + |
| 54 | +digest=$(echo -n $EMBEDDED_SEARCH_PARAMETERS_JSON | openssl dgst -sha256 -hmac $KEY_WITH_SEARCH_PERMISSIONS -binary | base64) |
| 55 | + |
| 56 | +scoped_api_key=$(echo -n "${digest}${KEY_WITH_SEARCH_PERMISSIONS:0:4}${EMBEDDED_SEARCH_PARAMETERS_JSON}" | base64) |
| 57 | + |
| 58 | +echo $scoped_api_key |
| 59 | +``` |
| 60 | + |
| 61 | + </template> |
| 62 | +</Tabs> |
| 63 | + |
| 64 | +Sample response: |
| 65 | + |
| 66 | +```json |
| 67 | +"RDhxa2VKTnBQVkxaVlFIOS9JWDZ2bDdtMU5HL3laa0pab2pTeEUzbFBhZz1STjIzeyJmaWx0ZXJfYnkiOiJjb21wYW55X2lkOjEyNCIsImV4cGlyZXNfYXQiOjE2MTE1OTA0NjV9" |
| 68 | +``` |
| 69 | + |
| 70 | +The `expires_at` parameter sets the expiration date for the API key and must be less that the expiration of parent API key. Let's perform a search using the scoped API key: |
| 71 | + |
| 72 | +<Tabs :tabs="['JavaScript','PHP','Python','Ruby']"> |
| 73 | + <template v-slot:JavaScript> |
| 74 | + |
| 75 | +```javascript |
| 76 | +let searchParameters = { |
| 77 | + 'q' : 'Stark', |
| 78 | + 'query_by' : 'company_name', |
| 79 | + 'sort_by' : 'num_employees:desc' |
| 80 | +} |
| 81 | + |
| 82 | +client.collections('companies') |
| 83 | + .documents() |
| 84 | + .search(searchParameters) |
| 85 | + .then(function (searchResults) { |
| 86 | + console.log(searchResults) |
| 87 | + }) |
| 88 | +``` |
| 89 | + </template> |
| 90 | + |
| 91 | + <template v-slot:PHP> |
| 92 | + |
| 93 | +```php |
| 94 | +$$searchParameters = [ |
| 95 | + 'q' => 'Stark', |
| 96 | + 'query_by' => 'company_name', |
| 97 | + 'sort_by' => 'num_employees:desc' |
| 98 | +] |
| 99 | + |
| 100 | +$client->collections['companies']->documents->search($searchParameters) |
| 101 | +``` |
| 102 | + </template> |
| 103 | + <template v-slot:Python> |
| 104 | + |
| 105 | +```python |
| 106 | +search_parameters = { |
| 107 | + 'q' : 'Stark', |
| 108 | + 'query_by' : 'company_name', |
| 109 | + 'sort_by' : 'num_employees:desc' |
| 110 | +} |
| 111 | + |
| 112 | +client.collections['companies'].documents.search(search_parameters) |
| 113 | +``` |
| 114 | + </template> |
| 115 | + <template v-slot:Ruby> |
| 116 | + |
| 117 | +```ruby |
| 118 | +search_parameters = { |
| 119 | + 'q' => 'Stark', |
| 120 | + 'query_by' => 'company_name', |
| 121 | + 'sort_by' => 'num_employees:desc' |
| 122 | +} |
| 123 | + |
| 124 | +client.collections['companies'].documents.search(search_parameters) |
| 125 | +``` |
| 126 | + </template> |
| 127 | +</Tabs> |
| 128 | + |
| 129 | + |
| 130 | +Response: |
| 131 | + |
| 132 | +```json |
| 133 | +{ |
| 134 | + "facet_counts": [], |
| 135 | + "found": 1, |
| 136 | + "hits": [ |
| 137 | + { |
| 138 | + "document": { |
| 139 | + "company_id": 124, |
| 140 | + "company_name": "Stark Industries", |
| 141 | + "country": "USA", |
| 142 | + "id": "0", |
| 143 | + "num_employees": 3355 |
| 144 | + }, |
| 145 | + "highlights": [ |
| 146 | + { |
| 147 | + "field": "company_name", |
| 148 | + "matched_tokens": [ |
| 149 | + "Stark" |
| 150 | + ], |
| 151 | + "snippet": "<mark>Stark</mark> Industries" |
| 152 | + } |
| 153 | + ], |
| 154 | + "text_match": 130816 |
| 155 | + } |
| 156 | + ], |
| 157 | + "out_of": 4, |
| 158 | + "page": 1, |
| 159 | + "request_params": { |
| 160 | + "collection_name": "companies", |
| 161 | + "per_page": 10, |
| 162 | + "q": "stark" |
| 163 | + }, |
| 164 | + "search_time_ms": 0 |
| 165 | +} |
| 166 | +``` |
| 167 | + |
| 168 | +As you see in the response, the document with `company_id` set to 124 is shown in the output. There is another document that has the `company_name` as "New Stark Industries", but it won't be shown in the result. |
| 169 | + |
| 170 | +You can find more details about scoped API keys [here](../../api/api-keys.html#generate-scoped-search-key). |
0 commit comments