Skip to main content

Middleware

Key0 provides access token validation middleware for every supported framework, plus a standalone validator for backend services that do not run the full SDK. All middleware functions extract the Bearer token from the Authorization header, verify it using the configured secret or public key, and attach the decoded payload to the request context. On failure they return a JSON error response and short-circuit the request.

Configuration Types

ValidateAccessTokenConfig

Used by the framework-specific middleware functions (validateAccessToken, honoValidateAccessToken, fastifyValidateAccessToken).
type ValidateAccessTokenConfig = {
  readonly secret: string;
};
FieldTypeDescription
secretstringHS256 shared secret. Must match the secret used by the AccessTokenIssuer.

ValidatorConfig

Used by the standalone validateKey0Token function. Supports both HS256 and RS256.
type ValidatorConfig = {
  secret?: string;
  publicKey?: string;
  algorithm?: "HS256" | "RS256";
};
FieldTypeDefaultDescription
secretstringShared secret for HS256. Required when algorithm is "HS256".
publicKeystringPEM-encoded public key for RS256. Required when algorithm is "RS256".
algorithm"HS256" | "RS256""HS256"Signing algorithm to expect.

AccessTokenPayload

All middleware functions resolve to the same decoded JWT payload shape.
type AccessTokenPayload = JWTPayload & {
  readonly sub: string;        // requestId
  readonly jti: string;        // challengeId
  readonly resourceId: string;
  readonly planId: string;
  readonly txHash: string;
};
ClaimTypeDescription
substringThe requestId that initiated the payment flow.
jtistringThe challengeId assigned by the challenge engine.
resourceIdstringIdentifier of the protected resource.
planIdstringThe plan the client paid for.
txHashstringOn-chain transaction hash of the USDC payment.
iatnumberIssued-at timestamp (seconds since epoch).
expnumberExpiration timestamp (seconds since epoch).

Framework Middleware

validateAccessToken

Express middleware. On success, attaches the decoded payload to req.key0Token.
import { validateAccessToken } from "@key0ai/key0/express";

app.use(
  "/api/photos",
  validateAccessToken({ secret: process.env.ACCESS_TOKEN_SECRET }),
);

app.get("/api/photos", (req, res) => {
  const token = req.key0Token;
  res.json({ planId: token.planId, txHash: token.txHash });
});
Signature:
function validateAccessToken(
  config: ValidateAccessTokenConfig,
): (req: Request, res: Response, next: NextFunction) => Promise<void>;
Request property: req.key0Token (AccessTokenPayload)

Internal: validateToken

The framework-agnostic function used by validateAccessToken, honoValidateAccessToken, and fastifyValidateAccessToken. You do not need to call this directly unless you are building a custom integration.
import { validateToken } from "@key0ai/key0";

async function validateToken(
  authHeader: string | null | undefined,
  config: ValidateAccessTokenConfig,
): Promise<AccessTokenPayload>;
Throws Key0Error with the following codes:
ScenarioError CodeHTTP Status
Missing or malformed Authorization headerINVALID_REQUEST401
Token signature expiredCHALLENGE_EXPIRED401
Invalid signature or malformed tokenINVALID_REQUEST401

Error Responses

All framework middleware functions return a consistent JSON error body on failure: 401 — Missing, expired, or invalid token:
{
  "type": "Error",
  "code": "INVALID_REQUEST",
  "message": "Missing or malformed Authorization header"
}
500 — Internal error (unexpected exception during verification):
{
  "type": "Error",
  "code": "INTERNAL_ERROR",
  "message": "Internal error"
}