Step 2: Configure Secure API Tokens

Overview

There are a few different scenarios in which you will want to create a token – Sites can mint the token for you, or you can mint the token yourself if you are not on Sites and just using Search.

  1. If you are hosting your Search experience on Sites, Sites will mint a token for you.
    • If you are using an OIDC method , Yext Auth , or basic password protection and NOT implementing Authorized Search, proceed to the Configure Secure Tokens Minted by Sites section below. Please refer to the relevant pre-requisite guides before getting started.
    • If you are using Yext Auth for a Private Site and Authorized Search experience, you can read more here about how your secure tokens should look and how you can configure Authorized Search with Yext.
  2. If you are creating a Search experience outside of Yext Sites, you’ll need to mint your own tokens, with the option of adding external identities (Pre-req here ) or logging in with Yext outside of Sites to add Yext identities (Pre-req here . Proceed to Mint your own Tokens.

Configure Secure Tokens Minted by Sites

We assume that you have already created an authentication policy resource with an auth policy for your Private Site and now would like to add secure tokens. Your policy should look something like the following. The apiToken Configuration is explained in the table below.

example-authentication-policy-with-tokens.json

{
  "$id": "example-authentication-policy-with-tokens",
  "$schema": "https://schema.yext.com/config/pages/authentication-policy/v1",
  "name": "example-authentication-policy-with-tokens",
  "authentication": {
    "password": {
      "passwordValue": "abc",
    }
  },
  "apiTokens": [
    { 
      "name": "SITE_SEARCH",
      "keyId": "482042fada",
      "claims": {
        "aud": "/v2/accounts/me/search",
        "expiresIn": 3600000, 
        "query": {
          "experienceKey": "Red_Dog_Spa",
          "experienceVersion": "ENV.DOMAIN_ENVIRONMENT"
        }
      },
      "scope": "/search-landing-page"
    } 
  ]
}

Understanding each part of the token

Property: name

  • type: string
  • Details: The name of your token. This can be anything, but take note of it as you will need it to configure your Search experience.
  • Validation: Required

Property: keyId

  • type: string
  • Details: This is the API key ID of a particular app that has permission to access the desired endpoint. You can find the KeyID in an app in Developer Console in the API Credentials Tab by clicking “Generate Custom Tokens with a Secret Key”.
  • Validation: Required

Property: claims

  • type: object
  • Details: The claims object configures what the token authorizes. You can see there are 3 claims you may want to configure:
    1. aud: Audience is a required claim that is a URL, or array of URLs, specifying which endpoint(s) the token is authorized to call. This must start with “/v2/accounts/” and be followed by a specified path up to at least a single endpoint. This should look like “/v2/accounts/{businessId}/endpoint/{optionalPath}” The businessId can be any businessId or just set to “me” to be the businessId calling the app. The optionalPath is any additional URL path that the token is restricted to, such as a single Stream.
    2. expiresIn: the expiration is in milliseconds and designates how long the token will last for after being minted by Sites. You can choose any value. For example, if you choose 360000 milliseconds (1 hour), the token will expire 1 hour after the time in which Sites mints it, which is when the user logs into the site. Setting the expiry time is optional. If unset, it will not expire
      • Note: you also have the option to set exp, or the expiry time in UNIX time, directly. This is NOT recommended, as all tokens for the experience will expire at the given time, rather than dynamically based on when the token is minted.
    3. query: this simply contains additional query params you may want to configure, such as the experienceKey or v param.
  • Validation: Required

Property: experienceVersion

  • Type: environment
  • Details: Optional but recommended and should remain unchanged. This is not truly a CaC variable, but rather set to ensure that a specific version is used and that previous versions of the configuration cannot be searched with the token.
    • In CaC, The version will be set based on the environment variable DOMAIN_ENVIRONMENT, either to STAGING or PRODUCTION.
    • IF YOU ARE MINTING YOUR OWN TOKEN: the experienceVersion should be set directly to either “STAGING” or “PRODUCTION”
  • Validation: Optional

Property: scope

  • Type: string
  • Details: A regex that specifies paths in the site. If set, the system will only generate tokens for pages matching the regex of this path. This is useful to improve performance, so that Sites does not have to mint a token for pages where it is not used. If not set, the system will generate a token for every page in the Site.
  • Validation: Optional

Once you have configured your token as you would like, you can apply your resource to the account!

Mint your own Tokens

Minting your own tokens can be done using a python or JWT library. After minting your tokens, you’ll need to pass it as an Authorization header to your Search GET request.

You can refer to the property table above for more details on each part of the token you are minting.

In javascript, you can import the jsonwebtoken library, and your code may look like the following:

import jwt from "jsonwebtoken";
/*
kid - key id
ss - signing secret
*/
const mintToken = (kid: string, ss: string) => {
 const token = jwt.sign(
   {
     aud: ["/v2/accounts/me/search"],
     query: {
		“experienceVersion”: “” //STAGING OR PRODUCTION
     },
   },
   ss,
   {
     expiresIn: "1hr",
     header: { kid }
   }
 );
 return token;
 };
 
mintToken("{INSERT KID}", "{INSERT API KEY}");

In python, you can import the PyJWT library , and your code may look like the following:

import requests
import jwt
from datetime import datetime, timedelta
 
KEY = "{API KEY}"
KEY_ID = "{API KEY ID}"
SEARCH_ENDPOINT = "https://cdn.yextapis.com/v2/accounts/me/search/vertical/query"
 
headers = {"kid": KEY_ID}
payload = {
   "aud": ["/accounts/me/search"],
   "exp": 32525530793,
   "query": {
		experienceVersion: “” #STAGING or PRODUCTION
         }
 }
 
token = jwt.encode(payload, KEY, headers=headers, algorithm="HS256")
 
#NOW MAKE YOUR GET REQUEST USING THE TOKEN YOU HAVE JUST MINTED
head = {'Authorization': f'Bearer {token}'}
params = {
   'input': 'external identities',
   'experienceKey': 'authorized-search-1',
   'verticalKey': 'faqs'
}
response = requests.get(SEARCH_ENDPOINT, params=params, headers=head)

Now Pass the Token to Search: See here for More information

Optional: Add Identities to your self-minted tokens

If you are minting your own tokens, you likely want to implement authorized search. This can be done at query time, and you’ll simply need to insert identity as a query param of your token.

External Identities
If you are not using the Yext system to store your users, then you’ll need to pass each user’s external identity/identities at query time as a part of the token. There are two parts to identity: the external source and the identity for that source. The reason for this is to ensure that, for example, my Google Drive entities respect Google-Drive permissions, and Salesforce entities respect Salesforce permissions, since there can oftentimes be an overlap in identities among different sources.

Each entity is tied to one source and a list of authorized users. If the source and at least one associated identity passed in the token matches that of the entity, then Search will surface it.

The token with external identities should look something like the following. You can have as many sources as you would like, and each source can have as many associated identities as needed. This should accurately represent all the identities that the user executing the search may have.

{  
  "identity": "{\"externalIdentities\":[{\"source\":\"insert_source_1\",\"identities\":[\"insert_identity_1\",\"insert_identity_2"]},{\"source\":\"insert_source_2\",\"identities\":[\"insert_identity_1\"]}]}"
}

If you are minting a token in JS with external identities, it may look like the following:

// Mint a Token with External Identities
query: {
     identity: JSON.stringify({
       externalIdentities: [
         {
            source:"google-drive",
            identities:[
               "mshaw@dm.com",
               "salesmanagers@dm.com",
               "dm.com"
            ]
         },
         {
            source:"dropbox",
            identities:[
               "maxshaw@dm.com",
               "sales-managers@dm.com",
               "sales-leaders"
            ]
         },
         {
            source:"sharepoint",
            identities:[
               "248420-sdf832f"
            ]
         }
      ]
   )}
}

Yext Identities
If you are using the Yext system to store your users, then at query time the Yext User’s identity will be included in the token. If you are using Yext Auth with Sites, this will automatically be done for you! If you happen to be using Yext Auth with Search OUTSIDE of Sites, then your token will need to include the following as part of the query:

{
  "identity": "{\"yextUserId\": \"INSERT_EXTERNAL_YEXT_USER_ID\"}"
}