Use search token authentication

This is for:

Developer

search tokens are special JSON web tokens that can be used to execute queries and send Coveo Analytics events as a specific user. They’re intended to be used in JavaScript code running in a web browser, typically along with Coveo Headless, Coveo Atomic, or the Coveo JavaScript Search Framework.

By default, search tokens automatically expire after 24 hours.

You can generate search tokens in server-side code by calling a REST service exposed through the Coveo Platform.

Typically, you’ll want to use search token authentication when your search page users are authenticated and some (or all) of the items in your index are secured. In this scenario, each user gets a unique search token, allowing the search interface to securely return only the items that the user is allowed to see.

Important

The examples on this page use the standard Coveo endpoint (platform.cloud.coveo.com).

If your organization is multi-region, has data residency outside the US, or is HIPAA-compliant, you must instead use the relevant endpoint.

Sample usage workflow

Here’s a typical workflow demonstrating the use of search tokens:

  1. A user requests a Coveo-powered search interface from a web server.

  2. The web server executes server-side code that eventually renders the HTML response (PHP, ASP.NET, and so on).

  3. Server-side code authenticates the user who is making the request.

  4. Server-side code calls a REST service exposed through the Coveo Platform to get a search token for the authenticated user.

  5. The search token is used to generate the JavaScript code that initializes the Coveo-powered search interface.

  6. The server sends the generated HTML to the client.

  7. The JavaScript code initializes the search interface and executes the first query, using the provided search token.

  8. The Coveo Platform executes the query as the user impersonated by the search token.

  9. Results are displayed to the user.

Node.js example

You could implement a Node.js Express middleware function and application to serve a Coveo-powered search interface configured with a valid search token.

Here’s a middleware function that requests a search token for the authenticated user:

// ./middleware.js
export const getSearchToken = async (req, _res, next) => {
  const orgId = '<ORG_ID>'; 1
  const apiKey = '<API_KEY>'; 2

  const userIds = [
    {
      name: 'jsmith@coveo.com', 3
      provider: 'Email Security Provider',
    },
  ]; 4

  const tokenEndpoint = `https://${orgId}.org.coveo.com/rest/search/v2/token?organizationId=${orgId}`; 5

  try {
    const request = {
      userIds,
    };

    const response = await fetch(tokenEndpoint, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
        Accept: 'application/json',
      },
      body: JSON.stringify(request),
    });

    if (response.ok) {
      const body = await response.json();
      req.token = body.token; 6
      req.orgId = orgId; 7
      next();
    } else {
      const errorBody = await response.text();
      next(
        new Error(
          `Failed to retrieve search token: ${response.statusText} (${response.status})\n${errorBody}`
        )
      );
    }
  } catch (error) {
    console.log('error', error);
    next(error);
  }
};
1 Replace this with your organization id.
2 Replace this value with an API Key that has the "Authenticated Search" scope. Important: Never store your API Key in code.
3 In practice, this information would be derived from the incoming request.
4 For details about constructing this list of userIds, please refer to the API reference documentation.
5 You may need to use a different endpoint, depending on your organization.
6 If the request to generate a token has succeeded, inject the token into the request payload so it can be consumed by the template.
7 Return the orgId defined in this file as part of the request payload to avoid defining it in multiple places.

The following Express Server calls the preceding middleware function when the / route is requested, and passes the generated search token to the template to render:

// ./index.js
import express from 'express';
import {getSearchToken} from './middleware.js';

const app = express();
app.set('view engine', 'ejs'); 1
app.get('/', getSearchToken, (req, res) => {
  res.render('search', {
    coveoHost: 'https://static.cloud.coveo.com/searchui', 2
    orgId: req.orgId, 3
    token: req.token, 4
  });
});
app.listen(3000);
1 Set the view engine for the express instance, which is ejs in this example.
2 Pass the hostname for the Coveo assets to avoid repetition.
3 Pass the organization id that was injected by the middleware.
4 Pass the search token generated for this request to use in calls to the Coveo Search API.

The search endpoint must then be configured with the search token before serving the page and sending queries to the Coveo Search API.

The following is a basic template for a JavaScript Search Framework-powered search page:

<!-- views/search.ejs -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <script class="coveo-script" src="<%- coveoHost %>/v2.10125/js/CoveoJsSearch.Lazy.min.js"></script>
    <link rel="stylesheet" href="<%- coveoHost %>/v2.10125/css/CoveoFullSearch.css" />
    <script src="<%- coveoHost %>/v2.10125/js/templates/templates.js"></script>
    <script>
      document.addEventListener("DOMContentLoaded", () => { 1
        Coveo.SearchEndpoint.configureCloudV2Endpoint(
          "<%- orgId %>",
          "<%- token %>",
        );

        Coveo.init(document.getElementById("search"));
      })
    </script>
  </head>
  <body id="search" class="CoveoSearchInterface">
    <!-- ... -->
    <div class="CoveoAnalytics"></div>
    <div class="coveo-search-section">
      <div class="CoveoSearchbox"></div>
    </div>
    <div class="coveo-main-section">
      <div class="CoveoResultList"></div>
    </div>
    <!-- ... -->
  </body>
</html>
1 If you have a HIPAA-compliant organization, you must modify your initialization code

If the requested page includes a Coveo In-Product Experience (IPX) interface, the token must be injected into the loader script URL as the value of the access_token query parameter.

The following is a basic template for a page that includes an IPX interface:

<!-- view/ipx.ejs -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- ... -->
    <!--  Coveo In-Product Experience -->
    <script
      async
      type="text/javascript"
      src="https://platform.cloud.coveo.com/rest/organizations/mycoveoorganization/pages/<PAGE_ID>/inappwidget/loader?access_token=<%- token %>"  1
    ></script>
    <!-- End Coveo In-Product Experience -->
    <!-- ... -->
  </head>
  <body>
    <!-- ... -->
  </body>
</html>
1 Replace <PAGE_ID> with the appropriate value.

Request a search token

To request a search token, make a POST request to https://<ORG_ID>.org.coveo.com/rest/search/v2/token. To test the service on the Coveo Platform, use the Swagger UI.

The caller must authenticate using an authenticated search API key. If you’re using an API key that enforces a specific search hub value, you won’t have to specify a searchHub value in the request parameter when creating the search token, as it will enforce the same value as the one in the API key.

Important

Never expose your authenticated search API key in client-side code. Always request search tokens in secure, server-side code.

Sample request

POST https://<ORG_ID>.org.coveo.com/rest/search/v2/token?organizationId=<ORG_ID> HTTP/1.1

Content-Type: application/json
Accept: application/json
Authorization: Bearer **********-****-****-****-************

Payload:

{
  "userIds": [
    {
      "name": "asmith@example.com",
      "provider": "Email Security Provider",
      "type": "User"
    }
  ]
}

Successful response

200 OK

{
  "token": "fzKjcHdjPJKJVaJ2OjK0fzK2CI6dHJ1ZSwiZXhwIjoxNDY4Njk2NzEwLCJpYXQiOjE0lQGN..."
}

Request body properties

Important

The RestTokenParams model exposes the salesforceCommunityUrl, salesforceFallbackToAdmin, salesforceUser, scope, and superUserToken attributes.

The RestUserId model exposes the authCookie, infos, and password attributes.

Don’t specify your own values for these attributes. They’re either intended for internal use by Coveo or exposed for legacy compatibility.

The body of a POST request to the /rest/search/v2/token route has the following properties:

userIds (array of RestUserId, required)

The security identities to impersonate when authenticating a query with this search token.

name (string, required)

The name of the security identity to impersonate.

This value can also be used in query pipeline condition statements (for example, when $identity is "asmith@example.com"). See Manage query pipeline conditions for more details.

Example: asmith@example.com

Tip
Leading practice

When generating a search token for a non-authenticated user, use anonymous as a security identity name.

Doing so will automatically set the anonymous parameter to true when logging events with that token.

provider (string, required)

Example: Email Security Provider

type (string, optional)

The type of the security identity to impersonate.

Default: User

Allowed values:

  • User

  • Group

  • VirtualGroup

  • Unknown

dictionaryFieldContext (object, optional)

The dictionary field context to enforce when authenticating a query with this search token. This value will override the dictionaryFieldContext parameter of the query itself.

A dictionary field context is a key-value store in which each pair corresponds to the name of a dictionary field to query, along with the key to target within that field.

For example, suppose that in your index, the @price dictionary field contains different values for its storeA and storeB keys. Including "dictionaryFieldContext": { "price": "storeA" } when creating a search token means that, for any query made with that search token, any part of the query expression that targets the @price field will in fact only query the storeA values of that field.

Important

If you make a dictionary field free-text searchable, all its values are indexed as free-text searchable. You should therefore avoid making dictionary fields free-text searchable if you want the values to remain secret.

allowedDictionaryFieldKeys (object, optional)

Specifies which dictionary field keys users can access when using field aliases.

Unlike dictionaryFieldContext, which selects a single key per dictionary field, field aliases let users retrieve multiple keys from the same dictionary field in a single request. The allowedDictionaryFieldKeys property controls which keys users are authorized to access.

The format is { "<FIELD_NAME>": [""] }, where [""] grants access to all keys in the specified dictionary field.

Note

Fine-grained access control for specific keys (for example, ["regular", "vip"]) is planned for a future release. Currently, only the wildcard ["*"] is supported.

Important

You can’t use allowedDictionaryFieldKeys (field aliases) and dictionaryFieldContext in the same request. Doing so returns an error.

allowedDictionaryFieldKeys sample call

Here’s the body of a search token creation call in which you authorize access to all keys in the price_list dictionary field:

{
  "userIds": [
    {
      "name": "asmith@example.com",
      "provider": "Email Security Provider"
    }
  ],
  "allowedDictionaryFieldKeys": {
    "price_list": ["*"]
  }
}

filter (string, optional)

The search result filter to apply when authenticating a query with this search token.

When provided, this query expression is a mandatory filter that is merged with the constant part of the query expression (cq) using an AND operator at the end of the query pipeline execution. Therefore, the filter can’t be used in query pipeline condition statements, since those conditions are evaluated before the filter token is applied.

For example, a condition such as when $constantQuery contains "@source==KnowledgeBase" won’t take into account the filter value of @source==KnowledgeBase.

Tip
Leading practice

Enforcing a filter in search tokens is a secure way to limit the scope of queries, since the filter is mandatory and can’t be altered client-side. However, this approach implies that the filter can’t be used to drive conditional logic in query pipelines.

A more flexible, albeit less secure, approach is to define query pipeline filter rules.

Depending on your needs, either query filtering approach can be legitimate. However, you should stick to the same approach for all search interfaces across your solution.

filter sample call

Here’s the body of a search token creation call in which you specify a constant query expression:

{
  "filter": "@source==KnowledgeBase",
  "userIds": [
    {
      "name": "asmith@example.com",
      "provider": "Email Security Provider"
    }
  ]
}

pipeline (string, optional)

The name of the query pipeline to use when authenticating a query with this search token.

This query pipeline will take precedence over the possible output of all other query pipeline routing mechanisms when using this search token.

Unless you specify a queryPipeline parameter when logging search or click events, the Coveo Analytics service will use the pipeline value defined in the search token.

Example: InternalSearch

Tip
Leading practice

Rather than enforcing a pipeline in the search token, consider enforcing a searchHub and using the condition-based query pipeline routing mechanism.

pipeline sample call

Here’s the body of a search token creation call in which you specify an enforced query pipeline:

{
  "pipeline": "InternalSearch",
  "userIds": [
    {
      "name": "asmith@example.com",
      "provider": "Email Security Provider"
    }
  ]
}

searchHub (string, optional)

The name of the search hub to enforce when authenticating a query with this search token.

This value will override the searchhub parameter of the query itself, and will be passed as the originLevel1 property value when logging Coveo Analytics search events.

The search hub can also be used in query pipeline condition statements (for example, when $searchHub is "CommunityHub"). See Manage query pipeline conditions for more details.

Example: SupportHub

Tip
Leading practice

Ideally, you should enforce the searchHub in the search token, and base the condition of each query pipeline in your Coveo organization on the searchHub value.

However, this isn’t recommended for search tokens that are used in Coveo for Commerce solutions. See Authenticate commerce requests for more information.

searchHub sample call

Here’s the body of a search token creation call in which you specify an enforced search hub:

{
  "searchHub": "SupportHub",
  "userIds": [
    {
      "name": "asmith@example.com",
      "provider": "Email Security Provider"
    }
  ]
}

userDisplayName (string, optional)

The userDisplayName to pass when logging Coveo Analytics search events.

This information is leveraged in the Analytics section of the Coveo Administration Console.

Example: Alice Smith

userDisplayName sample call

Here’s the body of a search token creation call in which you specify the user display name:

{
  "userDisplayName": "Alice Smith",
  "userIds": [
    {
      "name": "asmith@example.com",
      "provider": "Email Security Provider"
    }
  ]
}

userGroups (array of strings, optional)

The userGroups to pass when logging Coveo Analytics search events.

This information is leveraged in the Analytics section of the Coveo Administration Console.

User groups can be also be used in query pipeline condition statements (for example, when $groups contains "Employees"). See Manage query pipeline conditions for more details.

Example: ["Tech support agents", "Employees"]

userGroups sample call

Here’s the body of a search token creation call in which you specify user groups:

{
  "userGroups": ["Tech support agents", "Employees"],
  "userIds": [
    {
      "name": "asmith@example.com",
      "provider": "Email Security Provider"
    }
  ]
}

validFor (integer, optional)

The number of milliseconds the search token will remain valid for once it has been created.

Minimum value: 900000 (that is, 15 minutes)

Maximum/default: 86400000 (that is, 24 hours)

validFor sample call

Here’s the body of a search token creation call in which you specify the expiration time:

{
  "validFor": 3600000,
  "userIds": [
    {
      "name": "asmith@example.com",
      "provider": "Email Security Provider"
    }
  ]
}

Renew expired search tokens

By default, a search token expires after 24 hours. If a user opens a page that’s authenticated with a search token and doesn’t reload it for this length of time, the Search API will respond with the following error:

419 - Page Expired

{
  "message": "Expired token",
  "statusCode": 419,
  "type": "ExpiredTokenException"
}

If the search page is also configured to send events, the Usage Analytics Write API will respond with the following error:

400 - Bad Request

{
  "message": "The provided token is not a valid token.",
  "type": "InvalidToken"
}

You can usually renew expired search tokens automatically.[1]

In Headless, you can use the renewAccessToken option when you configure your engine.

In Atomic, you can use the renewAccessToken option when you initialize the atomic-search-interface component.

In the JavaScript Search Framework, specify a renewAccessToken function when configuring the search endpoint to use for your interface. The JavaScript Search Framework will call this function whenever a Search API or Usage Analytics Write API request returns the 419 - Page Expired HTTP status code. It will then reconfigure your search endpoint with the renewed token.

Note

The renewAccessToken function doesn’t take an argument. It must return a JavaScript Promise object that resolves to the renewed search token (that is, a string).

<!-- view/ipx.ejs -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- ... -->
    <script>
      document.addEventListener('DOMContentLoaded', () => {
        const renewAccessToken = async () => {
          const response = await fetch('https://example.com/token');
          return response.text(); 1
        };

        Coveo.SearchEndpoint.configureCloudV2Endpoint(
          '<%- orgId %>',
          '<%- token %>',
          {
            renewAccessToken,
          }
        );
        Coveo.init(document.body);
      });
    </script>
  </head>
  <body>
    <!-- ... -->
  </body>
</html>
1 This example assumes that the search token returned is in text/plain format.

1. It’s not currently possible to renew expired search tokens for IPX interfaces.