Skip to main content

Token verifier

@kittl/sdk-backend helper for verifying short-lived Kittl user JWTs from kittl.auth.getUserToken(). See the SDK backend overview for install and the typical flow.

API

  • new KittlSDK({ appId, cacheMaxAgeMs?, cacheMaxEntries?, signingKeyCache?, onSigningKeyCacheError?, timeoutMs? }): create a backend SDK instance for your app
  • SigningKeyCache: optional external cache for resolved PEM signing keys
  • kittlBackend.verifyUserToken(token): verify a JWT and return { sub, aud, iss, iat, exp }
  • TokenInvalidError / TokenInvalidErrorCode: thrown when verification fails

End-to-end flow

  1. In your app frontend, call kittl.auth.getUserToken() to get a short-lived JWT for the current Kittl-authenticated user.
  2. Send that token to your backend with the request you want to authorize.
  3. On your backend, verify the token with @kittl/sdk-backend before handling the request.

Frontend: get a Kittl user token

import { kittl } from '@kittl/sdk';

const tokenResp = await kittl.auth.getUserToken();

if (!tokenResp.isOk) {
// Handle tokenResp.error
return;
}

const { token } = tokenResp.result;

await fetch('https://your-backend.example/api/do-something', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
},
});

getUserToken() returns a short-lived JWT representing the current Kittl-authenticated user for your app. This is separate from OAuth provider tokens you may retrieve with kittl.auth.getAuthToken().

Backend: verify the Kittl user token

Initialize once (for example at module load time), then verify the token your frontend obtained from getUserToken():

import { KittlSDK, TokenInvalidError } from '@kittl/sdk-backend';

const kittlBackend = new KittlSDK({
appId: process.env.KITTL_APP_ID!,
});

try {
const payload = await kittlBackend.verifyUserToken(token);
console.log(payload.sub);
} catch (error) {
if (error instanceof TokenInvalidError) {
// Return 401 Unauthorized.
}

throw error;
}

appId is used as the JWT audience, so tokens issued for a different app are rejected. The verified token's sub claim is a pseudonymous per-app user ID.

How verification works

The verifier:

  • fetches signing keys from the Kittl API JWKS endpoint for your app
  • validates RS256 signatures, issuer, audience, and standard JWT expiry claims
  • returns a typed payload with sub, aud, iss, iat, and exp

Error handling

Verification failures throw TokenInvalidError. Use error.code to distinguish cases such as expired tokens or missing signing keys:

CodeWhen
invalid_optionsMissing or invalid verifier options (for example, no appId)
invalid_tokenToken failed signature or claim validation
missing_key_idJWT header is missing kid
signing_key_unavailableJWKS lookup failed
token_expiredToken exp is in the past
token_not_activeToken nbf is in the future
unexpected_payloadVerified JWT is missing required claims

Optional tuning

KittlSDK accepts optional cache and timeout settings:

const kittlBackend = new KittlSDK({
appId: process.env.KITTL_APP_ID!,
cacheMaxAgeMs: 60 * 60 * 1000,
cacheMaxEntries: 5,
timeoutMs: 5_000,
});

By default, signing keys are cached in memory per SDK instance for up to cacheMaxAgeMs (default: 1 hour) and up to cacheMaxEntries keys (default: 5).

Persistent signing key cache

When your runtime does not reuse process memory between requests, provide a persistent signingKeyCache to reuse keys across restarts and cold starts — for example on edge, serverless, or other ephemeral backends. The adapter is checked before the SDK's in-memory JWKS cache, so its get method runs on the token verification path — prefer low-latency storage and add local memoization if remote I/O per verification would be too expensive.

import { KittlSDK, type SigningKeyCache } from '@kittl/sdk-backend';

const signingKeyCache: SigningKeyCache = {
async get(key) {
return await store.get(key);
},
async set(key, publicKeyPem, { ttlMs }) {
await store.set(key, publicKeyPem, { ttlMs });
},
};

const kittlBackend = new KittlSDK({
appId: process.env.KITTL_APP_ID!,
signingKeyCache,
onSigningKeyCacheError(error, context) {
logger.warn({ error, ...context }, 'Kittl signing key cache error');
},
});

Cache errors are reported through onSigningKeyCacheError and do not fail token verification; the SDK falls back to its in-memory signing key cache, then to Kittl's JWKS endpoint when needed.