Secured API keys in Angular InstantSearch
Angular InstantSearch isn’t compatible with Angular’s Ivy view engine. We’re investigating how best to support this. For more information and to vote for Algolia’s support of Angular 16 and beyond, see the GitHub issue Algolia Support for Angular InstantSearch
On this page
Search-only API keys on web
Inside your app you can use the search-only API key. You can include this key directly in your frontend code. It might happen that you need to apply some rate limiting to your API Key. For those cases, you can generate secured API keys.
Secured API keys
Secured API Keys are useful when you want to restrict the search to a set of indices, for example. Those keys need to be generated on the backend. Otherwise, users can modify the restrictions of the key by modifying the frontend code. This guide doesn’t focus on how to use Secured API Keys, but rather on the usage of those keys in an InstantSearch app. The aim is to implement an app that generates a secured API key on the server, then uses it on the client. The secured API Key will have a restriction on the index. You can find the complete example on GitHub.
Server
Generate the secured API key
The first step is to generate a secured API key on the server. This example uses Express but the concepts could be applied to any server. To generate a key, you need the Algolia JavaScript client and a “parent key” that has the search ACL. Most of the time this “parent key” will be the search-only API Key. In this example, the key is restricted to the index demo_ecommerce
. It means that with this key the frontend won’t be able to target a different index. You can apply different kind of restrictions and also apply a set of search parameters.
1
2
3
4
5
6
const algoliasearch = require('algoliasearch');
const client = algoliasearch('YourApplicationID', 'YourSearchOnlyAPIKey');
const securedApiKey = client.generateSecuredApiKey('YourSearchOnlyAPIKey', {
restrictIndices: 'demo_ecommerce',
});
Once you have the key, you must pass it to the client with one of two implementations:
- Create an endpoint on your server that will send back the API Key. Then on the client before creating the app you need to asynchronously fetch the API key from the server.
- Inline the API key in the HTML. Then, on the client, you can directly create the app by reading the value from the global object.
This guide uses the second option: inline the API key.
Inline the API key
For simplicity, this example uses Parcel. In Parcel, you don’t create the HTML file from scratch but use the one generated by the CLI. To inject data from the server, use a placeholder value in the HTML. Note that this step is only useful if you are using an app generated from a CLI. Otherwise you can directly inline the value inside your HTML template.
1
2
3
4
5
6
7
8
<!DOCTYPE html>
<html lang="en">
<head>
<script>
window.SERVER_DATA = __SERVER_DATA__;
</script>
</head>
</html>
Once the placeholder is set up, it can be replaced with the generated API key.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const path = require('path');
const fs = require('fs');
const util = require('util');
const express = require('express');
const app = express();
const readFileAsync = util.promisify(fs.readFile);
app.get('/', async (_, res) => {
const index = await readFileAsync(
path.join(__dirname, 'dist', 'index.html'),
'utf-8'
);
const indexWithServerData = index.replace(
'__SERVER_DATA__',
JSON.stringify({
ALGOLIA_API_KEY: securedApiKey,
})
);
res.send(indexWithServerData);
});
That’s it for the server. You should now be able to run the server and access your generated API key from the client with window.SERVER_DATA
. The next section of this guide describes how to use this key in an InstantSearch app.
Client
Retrieve the API key
Now that you have the API key on the global object, you can retrieve it from the client code and inject it into searchClient
. Make sure you clean up the global object. Otherwise, this value will stay in memory.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import algoliasearch from 'algoliasearch/lite';
@Component({
template: `
<ais-instantsearch [config]="config">
<!-- Widgets -->
</ais-instantsearch>
`,
})
export class AppComponent {
config = {
indexName: 'demo_ecommerce',
searchClient: algoliasearch('AJ0P3S7DWQ', (window as any).SERVER_DATA.ALGOLIA_API_KEY),
}
}
That’s it for the client. Your app can now only target the index demo_ecommerce
. You can try to target a different one like demo_media
, but the API will return an error.
XSS - Preventing attacks from within user generated content
Algolia handles highlighting within the engine. By leveraging this feature, you give your user a way to know how their query matches the results, it is a very important cue.
Technically this means that the engine will surround the matching words with tags. By default, Algolia uses simple HTML tags like em
and lets the browser render the content as HTML. This leaves a potential security hole, especially in the context of user generated content.
The same problem exists for the snippeting feature.
To fix this, InstantSearch will force the use of a known set of HTML tags and will escape all tags not in this set. This means that if you’ve set a custom tag in the Algolia dashboard, it will be overridden and replaced by mark
. If you want to use another one, like for example strong
, you need to specify the highlightedTagName
props in ais-highlight
or ais-snippet
.
1
2
3
4
5
6
7
8
9
<ais-instantsearch [config]="config">
<ais-hits>
<ng-template let-hits="hits" let-results="results">
<div *ngFor="let hit of hits">
<ais-highlight attribute="name" tagName="strong" [hit]="hit"></ais-highlight>
</div>
</ng-template>
</ais-hits>
</ais-instantsearch>