Skip to main content

AccessTokenIssuer

AccessTokenIssuer handles JWT creation and verification for the Key0 payment flow. After a client completes an on-chain USDC payment, the engine uses AccessTokenIssuer to mint a signed JWT that grants access to the purchased resource. It supports two signing algorithms:
  • HS256 — symmetric shared secret (default). Suitable for single-service deployments.
  • RS256 — asymmetric RSA key pair. Suitable for distributed systems where multiple services verify tokens using the public key.
import { AccessTokenIssuer } from "@key0ai/key0";

Constructor

const issuer = new AccessTokenIssuer("your-secret-at-least-32-characters-long");

Parameters

config
AccessTokenIssuerConfig | string
required
Either a plain string (interpreted as an HS256 shared secret) or a configuration object.

AccessTokenIssuerConfig

PropertyTypeRequiredDescription
secretstringWhen using HS256Shared secret. Must be at least 32 characters.
privateKeystringWhen using RS256RSA private key in PEM (PKCS#8) format.
algorithm"HS256" | "RS256"NoSigning algorithm. Defaults to "HS256".

Validation

The constructor throws immediately if:
  • An HS256 secret is shorter than 32 characters.
  • RS256 is selected but no privateKey is provided.
  • HS256 is selected (or defaulted) but no secret is provided.

Methods

sign

Signs a JWT containing the provided claims.
sign(claims: TokenClaims, ttlSeconds: number): Promise<TokenResult>
claims
TokenClaims
required
The claims to embed in the JWT payload. See TokenClaims below.
ttlSeconds
number
required
Token time-to-live in seconds. The exp claim is set to iat + ttlSeconds.
token
string
The signed JWT string.
Returns Promise<TokenResult> — an object with a single token property.

verify

Verifies a JWT signed with HS256 and returns the decoded payload.
verify(token: string): Promise<TokenClaims & { iat: number; exp: number }>
token
string
required
The JWT string to verify.
Returns the decoded payload including standard iat and exp claims.
This method only supports HS256. Calling verify on an RS256 issuer throws an error. For RS256 token verification, use validateKey0Token from the middleware layer, which accepts a public key.

verifyWithFallback

Attempts verification with the primary secret first, then iterates through fallback secrets. Designed for zero-downtime secret rotation.
verifyWithFallback(
  token: string,
  fallbackSecrets: string[]
): Promise<TokenClaims & { iat: number; exp: number }>
token
string
required
The JWT string to verify.
fallbackSecrets
string[]
required
An ordered list of previous secrets to try if the primary secret fails.
Returns the decoded payload if any secret succeeds. Throws "Token verification failed with all secrets" if the primary and all fallback secrets fail.

Types

TokenClaims

Claims embedded in every Key0 access token.
ClaimTypeDescription
substringThe requestId that initiated the payment flow.
jtistringThe challengeId (used for replay prevention).
resourceIdstringIdentifier of the purchased resource.
planIdstringIdentifier of the purchased plan.
txHashstringOn-chain USDC transaction hash that funded the access.
Standard JWT claims iat (issued at) and exp (expiration) are set automatically by sign and included in the return type of verify and verifyWithFallback.

TokenResult

type TokenResult = {
  readonly token: string;
};

Usage

Signing and verifying a token

import { AccessTokenIssuer } from "@key0ai/key0";

const issuer = new AccessTokenIssuer("a-secret-that-is-at-least-32-characters");

// Sign a token after successful payment
const { token } = await issuer.sign(
  {
    sub: "req_abc123",
    jti: "ch_xyz789",
    resourceId: "weather-api",
    planId: "plan_basic",
    txHash: "0x1234...abcd",
  },
  3600, // 1 hour TTL
);

// Verify the token on a subsequent request
const claims = await issuer.verify(token);
console.log(claims.resourceId); // "weather-api"
console.log(claims.exp);        // Unix timestamp

Zero-downtime secret rotation

When rotating secrets, pass the old secret(s) as fallbacks so that tokens signed with the previous secret remain valid until they expire.
const currentSecret = process.env.ACCESS_TOKEN_SECRET!;
const previousSecrets = [process.env.ACCESS_TOKEN_SECRET_OLD!];

const issuer = new AccessTokenIssuer(currentSecret);

// New tokens are signed with currentSecret
const { token } = await issuer.sign(claims, 3600);

// Verification accepts tokens signed with either secret
const decoded = await issuer.verifyWithFallback(token, previousSecrets);

RS256 signing

const issuer = new AccessTokenIssuer({
  privateKey: process.env.RSA_PRIVATE_KEY!,
  algorithm: "RS256",
});

const { token } = await issuer.sign(claims, 3600);
// Distribute the corresponding public key to verifying services