Add user authentication and authorization using JWT Tokens

Sometimes, you may want to authenticate a user when calling the search and chat API. Typical scenarios include:

  • Restrict answers of the bot to content that a user has access to. Typically applicable for products with enterprise tiers or private documentation.
  • Provide context about the user or apply filters to scope a conversation to a known context in a way that cannot be tampered.

The Inkeep search and chat API provides the ability to authenticate users by providing a User-Token header that contains a JWT token identifying the user.

Example headers:

{
  "Authorization": "Bearer <YOUR_INKEEP_INTEGRATION_API_KEY>"
  "User-Token": "<JWT_TOKEN_SIGNED_BY_YOUR_BACKEND>"
}

Configuring the Integration

Generate a private and public key pair using openssl or the method of your choice.

openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in private_key.pem -out public_key.pem

The private key will be used by your backend service to mint tokens that are passed to the Inkeep API. It should not be shared with any other service or party.

In your Inkeep Integration, configure the following:

  • Require user authentication: set to true
  • Public key: provide the value of public_key.pem
  • Issuer: the value we should ensure matches the iss claim in the authentication token. Example: https://auth.mydomain.com. This is just a string URI, it can be arbitrary, it just needs to uniquely identify your service.

We will use the public key to validate the token that is provided to our service was created (‘minted’) by your service. We check the signature of the token and the iss, exp, aud and other standard claims.

Token Claims

The token should include the following claims:

  • iss: The issuer of the token. Example https://auth.mydomain.com.
  • aud: The audience of the token. It should always be https://api.inkeep.com.
  • integrationId: The ID of the integration. This must match the integration ID in the body of the request.
  • filters: Same as documented in the (Start new chat)[/inkeep-api/create-new-chat-session#chatfiltersinput] request body. Takes precedence over the filters value provided in the request body.
  • userAttributes: Same as documented in the (Start new chat)[/inkeep-api/create-new-chat-session#newsessionchatresultinput] request body. Takes precedence over the userAttributes value provided in the request body.
  • exp: The expiration time of the token, in seconds since the epoch.

The Authorization : Bearer {integrationApiKey} header must still be present in the request.

Generating a Token

Here is an example of how to generate a token in Node.js:

import fs from "fs";
import jwt from "jsonwebtoken";

interface TokenConfiguration {
  iss: string;
  aud: string;
  integrationId: string;
  filters: {
    attributes: {
      $and: Array<{
        [key: string]: string | { $in: string[] };
      }>;
    };
  };
  userAttributes?: {
    [key: string]: string | number | string[];
  };
  expirationOffsetHours?: number;
}

const generateToken = ({
  iss,
  aud,
  integrationId,
  filters,
  userAttributes,
  expirationOffsetHours = 24,
}: TokenConfiguration): string => {
  const privateKey = fs.readFileSync("private_key.pem", "utf8"); // your private key

  const payload = {
    iss,
    aud: "https://api.inkeep.com",
    integrationId,
    filters,
    userAttributes,
    exp: Math.floor(Date.now() / 1000) + expirationOffsetHours * 60 * 60,
  };

  const token = jwt.sign(payload, privateKey, { algorithm: "RS256" });

  return token;
};

const exampleConfiguration: TokenConfiguration = {
  iss: "https://auth.mydomain.com",
  integrationId: "inkeep-integration-id",
  filters: {
    attributes: {
      $and: [
        {
          env: "dev",
        },
        {
          modules: {
            $in: ["module1", "module2"],
          },
        },
      ],
    },
  },
  userAttributes: {
    userId: "123456789",
    userEmail: "me@inkeep.com",
    userCohorts: [`group1`, `group2`],
  },
  expirationOffsetHours: 24,
};

const token = generateToken(exampleConfiguration);