Integrate Kliento in your clients

Kliento is a client authentication protocol that eliminates the need for shared secrets (e.g. API keys) whilst removing the requirement for servers to configure or retrieve trusted public keys (unlike JWTs).

When using Kliento, clients obtain or generate token bundles that will then be presented to servers. These bundles include cryptographic signatures that can be verified server-side without accessing remote services or pre-configuring trust relationships.

This guide explains how to implement Kliento in client applications across various programming languages and platforms.

Using VeraId Authority

VeraId Authority is the recommended approach for obtaining these credentials as it eliminates the need to manage private keys directly, leverages existing identity providers (e.g. GitHub, AWS, GCP, Kubernetes), and provides short-lived credentials via JWT exchange.

Before clients can exchange JWTs for token bundles, you must first create signature specs. A signature spec defines what entity can request signatures, what service the signatures are for, and what plaintext should be signed. This allows controlled, secure delegation of signing authority without exposing private keys. Here’s an example of creating a signature spec:

POST /orgs/example.com/members/123/signature-specs HTTP/1.1
HOST: veraid-authority.example
Authorization: Bearer <JWT>
Content-Type: application/json

{
  "auth": {
    "type": "oidc-discovery",
    "providerIssuerUrl": "https://accounts.google.com",
    "jwtSubjectClaim": "email",
    "jwtSubjectValue": "app@acme.iam.gserviceaccount.com"
  },
  "serviceOid": "1.3.6.1.4.1.58708.1.1",
  "ttlSeconds": 300,
  "plaintext": "eyJhdWRpZW5jZSI6Imh0dHA6Ly9sb2NhbGhvc3QvIn0K"
}

Note that plaintext represents the base64-encoded value that will be signed, which in the case of Kliento must be a JSON object with the properties audience (a string identifying the target server) and claims (an optional object containing a key/value pair for each claim). In the example above, plaintext represents the base64 encoding of {"audience":"https://localhost/"}.

The response to the request above will be a JSON object containing the URL that your client will use to exchange token bundles (exchangeUrl).

Refer to the Credentials Exchange API documentation for more information.

JavaScript

JS clients can use the @veraid/authority-credentials library to automate token bundle provisioning. This library automatically detects and exchanges credentials from various identity providers (called “exchangers”), including GitHub Actions, AWS, GCP, and more.

For example, the following code initialises the exchanger for GitHub Actions and makes authenticated HTTP requests:

import { initExchangerFromEnv } from '@veraid/authority-credentials';

// Replace with the actual URL for exchanging credentials
const EXCHANGE_ENDPOINT = new URL('https://veraid-authority.example/creds/123');

// Replace 'GITHUB' with the exchanger you want
const exchanger = initExchangerFromEnv('GITHUB');

// Make requests that use the Kliento token bundle
export async function authFetch(request: Request) {
  const { credential: tokenBundle } = await exchanger.exchange(EXCHANGE_ENDPOINT);
  const headers = { 'Authorization': `Kliento ${tokenBundle.toString('base64')}` }
  return fetch(request, { headers })
}

Although this example obtains a token bundle for each request, you could reuse it for as long as it is valid (up to 5 minutes in your signature spec).

GitHub Actions

Use CheVeraId/authority-credentials-action to obtain token bundles in GitHub workflows:

jobs:
  authentication:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
    steps:
      - name: Get Kliento token bundle
        id: token-bundle
        uses: CheVeraId/authority-credentials-action@v1
        with:
          exchange-endpoint: https://veraid-authority.example/creds/123
      - name: Use token bundle
        run: curl -H "Authorization: Kliento ${TOKEN_BUNDLE}" https://api.example.com
        environment:
          TOKEN_BUNDLE: ${{ steps.token-bundle.outputs.credential }}

Note that you must grant the workflow the id-token: write permission to enable OIDC token generation.

Unsupported languages

VeraId Authority uses OpenID Connect Discovery to authenticate clients requesting credentials. This allows integration with Amazon Cognito, Azure, GCP, Kubernetes, and many other identity providers.

For custom implementations, send your workload identity’s JWT to obtain a token bundle:

GET /creds/123 HTTP/1.1
HOST: veraid-authority.example
Accept: application/vnd.veraid.signature-bundle+base64
Authorization: Bearer <JWT>

The response will contain a base64-encoded token bundle which can be used in the Authorization header with the Kliento scheme. If you wish to get the token bundle in binary format, omit the Accept header.

Without VeraId Authority

In a future where Kliento becomes widely adopted, many clients may not need VeraId Authority if their cloud provider issues Kliento token bundles directly. For example, GitHub could issue token bundles for GitHub Actions workloads with identifiers like your-repo@your-org.github.io.

Until then, if you don’t want to use VeraId Authority, you will have to manage the issuance of VeraId signature bundles that represent Kliento token bundles using a VeraId library like @relaycorp/veraid. You’ll basically have to generate a key pair for your organisation, and then use the SDK to generate the VeraId TXT record and issue Kliento token bundles using your organisation’s private key.

Since your client must now securely manage a private key, we recommend using a Key Management Service (KMS) like AWS KMS. If you’re using JavaScript, consider using the @relaycorp/webcrypto-kms library.

Generate VeraId TXT record

The value of the VeraId TXT record is mostly derived from the public key of your organisation, but it also contains the maximum validity period of the DNSSEC chain.

For example, you can use the VeraId JavaScript library to generate such values as follows:

import { generateTxtRdata } from "@relaycorp/veraid";

const TTL_OVERRIDE_SECONDS = 3600; // 1 hour

const publicKey = await getYourPublicKey();
const txtRecord = await generateTxtRdata(publicKey, TTL_OVERRIDE_SECONDS);

You’d then paste the result into the TXT record of your _veraid.<your-domain> subdomain. Evidently, you only need to do this once.

Issue Kliento token bundles

Once your domain is properly configured, your client can issue Kliento token bundles using the organisation’s private key. This basically involves generating VeraId signature bundles that encapsulate Kliento tokens (the end result being the token bundles).

For example, the VeraId JavaScript library could be used with the @veraid/kliento library to issue Kliento token bundles as follows:

import {
  OrganisationSigner,
  selfIssueOrganisationCertificate,
  VeraidDnssecChain,
} from "@relaycorp/veraid";
import { Token, TokenBundle } from "@veraid/kliento";

// VeraId configuration
const ORG_NAME = "your-company.com";
const MEMBER_NAME = "alice";
const TTL_SECONDS = 300; // 5 minutes

// Kliento configuration
const AUDIENCE = "https://localhost/";
const TOKEN = new Token(AUDIENCE, { claim1: "value1" });

async function issueTokenBundle() {
  const domainName = `${ORG_NAME}.`;

  // Retrieve DNSSEC chain or retrieve it from cache
  const dnssecChain = await VeraidDnssecChain.retrieve(domainName);

  // Issue organisation certificate or retrieve it from cache
  const keyPair = await getYourKeyPair();
  const expiry = new Date(Date.now() + TTL_SECONDS * 1000);
  const certificate = await selfIssueOrganisationCertificate(domainName, keyPair, expiry);

  // Finally, issue token bundle and return it as an ArrayBuffer
  const signer = new OrganisationSigner(dnssecChain, certificate, MEMBER_NAME);
  const tokenBundle = await TokenBundle.sign(TOKEN, keyPair.privateKey, signer, expiry);
  return tokenBundle.serialise();
}

If you went down this route, we’d love to hear about it on r/VeraId so we can see if we can make things easier in the future.