From ca326df0af674c57a1d5d98b80ca48f2fd394ba3 Mon Sep 17 00:00:00 2001 From: Mehmet Kar Date: Mon, 17 Aug 2026 00:39:32 +0300 Subject: [PATCH] feat(jwt): allow custom typ in JwtHeader and createJwt Allow callers to specify custom JWT 'typ' headers (such as 'kya+JWT' for Skyfire KYA tokens) in JwtHeader, createJwt, and schema validators. Resolves the TODO in the skyfire-kya demo. AI disclosure: Implemented with the assistance of Antigravity AI coding assistant. --- .changeset/allow-custom-jwt-typ.md | 5 +++++ demos/skyfire-kya/src/kya-token.ts | 1 - packages/jwt/src/create-jwt.test.ts | 18 ++++++++++++++++++ packages/jwt/src/create-jwt.ts | 5 +++-- packages/jwt/src/jwt-algorithm.ts | 3 ++- packages/jwt/src/schemas/valibot.ts | 4 ++-- packages/jwt/src/schemas/zod.ts | 2 +- 7 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 .changeset/allow-custom-jwt-typ.md diff --git a/.changeset/allow-custom-jwt-typ.md b/.changeset/allow-custom-jwt-typ.md new file mode 100644 index 00000000..c04e4033 --- /dev/null +++ b/.changeset/allow-custom-jwt-typ.md @@ -0,0 +1,5 @@ +--- +"@agentcommercekit/jwt": patch +--- + +Allow optional custom `typ` header in `JwtHeader` and `createJwt` to support specialized JWT profiles (such as Skyfire KYA tokens). diff --git a/demos/skyfire-kya/src/kya-token.ts b/demos/skyfire-kya/src/kya-token.ts index dc07fe3d..3580de74 100644 --- a/demos/skyfire-kya/src/kya-token.ts +++ b/demos/skyfire-kya/src/kya-token.ts @@ -53,7 +53,6 @@ export async function createMockSkyfireKyaToken( expiresIn: 3600, }, { - // @ts-expect-error - TODO: fix this typ: "kya+JWT", alg: "ES256", }, diff --git a/packages/jwt/src/create-jwt.test.ts b/packages/jwt/src/create-jwt.test.ts index 5ae2cf8e..3b370096 100644 --- a/packages/jwt/src/create-jwt.test.ts +++ b/packages/jwt/src/create-jwt.test.ts @@ -55,4 +55,22 @@ describe("createJWT", () => { "Failed to create JWT", ) }) + + it("creates a JWT with custom typ and alg header overrides", async () => { + const expectedJwt = + "eyJ0eXAiOiJreWErSldUIiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJkaWQ6ZXhhbXBsZTo0NTYifQ.sig" + + vi.mocked(baseCreateJWT).mockResolvedValueOnce(expectedJwt) + + const result = await createJwt(mockPayload, mockOptions, { + typ: "kya+JWT", + alg: "ES256", + }) + + expect(result).toBe(expectedJwt) + expect(baseCreateJWT).toHaveBeenCalledWith(mockPayload, mockOptions, { + typ: "kya+JWT", + alg: "ES256", + }) + }) }) diff --git a/packages/jwt/src/create-jwt.ts b/packages/jwt/src/create-jwt.ts index e99d50d9..2c5f6324 100644 --- a/packages/jwt/src/create-jwt.ts +++ b/packages/jwt/src/create-jwt.ts @@ -15,7 +15,7 @@ export type JwtOptions = JWTOptions * JWT header that only contains valid JWT algorithms */ export interface JwtHeader extends Omit { - typ: "JWT" + typ?: string alg: JwtAlgorithm } @@ -35,10 +35,11 @@ export async function createJwt( options: JwtOptions, { alg = "ES256K", ...header }: Partial = {}, ): Promise { + // oxlint-disable-next-line typescript/no-unsafe-type-assertion const result = await baseCreateJWT(payload, options, { ...header, alg, - }) + } as Partial) if (!isJwtString(result)) { throw new Error("Failed to create JWT") diff --git a/packages/jwt/src/jwt-algorithm.ts b/packages/jwt/src/jwt-algorithm.ts index e68a1b59..dcba60ef 100644 --- a/packages/jwt/src/jwt-algorithm.ts +++ b/packages/jwt/src/jwt-algorithm.ts @@ -3,7 +3,8 @@ import { isKeyCurve, type KeyCurve } from "@agentcommercekit/keys" /** * JWT signing algorithms supported by the JWT library * - * The did-jwt library also supports non-standard "ES256K-R" for + * The did-jwt library also supports non-standard "ES256K-R" for recovery + * signatures, which ACK does not support. */ export const jwtAlgorithms = ["ES256", "ES256K", "EdDSA"] as const export type JwtAlgorithm = (typeof jwtAlgorithms)[number] diff --git a/packages/jwt/src/schemas/valibot.ts b/packages/jwt/src/schemas/valibot.ts index c3dcb5ac..545ea1c0 100644 --- a/packages/jwt/src/schemas/valibot.ts +++ b/packages/jwt/src/schemas/valibot.ts @@ -19,10 +19,10 @@ export const jwtPayloadSchema = JwtPayloadSchema */ export const jwtHeaderSchema = v.pipe( v.looseObject({ - typ: v.literal("JWT"), + typ: v.optional(v.string()), alg: v.picklist(jwtAlgorithms), }), - v.custom(() => true), + v.custom((_val): _val is JwtHeader => true), ) /** diff --git a/packages/jwt/src/schemas/zod.ts b/packages/jwt/src/schemas/zod.ts index 0581e6e4..c7237d3f 100644 --- a/packages/jwt/src/schemas/zod.ts +++ b/packages/jwt/src/schemas/zod.ts @@ -22,7 +22,7 @@ export const jwtPayloadSchema = JwtPayloadSchema */ export const jwtHeaderSchema = z .looseObject({ - typ: z.literal("JWT"), + typ: z.string().optional(), alg: z.enum(jwtAlgorithms), }) .refine((_val): _val is JwtHeader => true)