Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/allow-custom-jwt-typ.md
Original file line number Diff line number Diff line change
@@ -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).
1 change: 0 additions & 1 deletion demos/skyfire-kya/src/kya-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ export async function createMockSkyfireKyaToken(
expiresIn: 3600,
},
{
// @ts-expect-error - TODO: fix this
typ: "kya+JWT",
alg: "ES256",
},
Expand Down
18 changes: 18 additions & 0 deletions packages/jwt/src/create-jwt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
})
})
})
5 changes: 3 additions & 2 deletions packages/jwt/src/create-jwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type JwtOptions = JWTOptions
* JWT header that only contains valid JWT algorithms
*/
export interface JwtHeader extends Omit<JWTHeader, "alg" | "typ"> {
typ: "JWT"
typ?: string
alg: JwtAlgorithm
}

Expand All @@ -35,10 +35,11 @@ export async function createJwt(
options: JwtOptions,
{ alg = "ES256K", ...header }: Partial<JwtHeader> = {},
): Promise<JwtString> {
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
const result = await baseCreateJWT(payload, options, {
...header,
alg,
})
} as Partial<JWTHeader>)

if (!isJwtString(result)) {
throw new Error("Failed to create JWT")
Expand Down
3 changes: 2 additions & 1 deletion packages/jwt/src/jwt-algorithm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
4 changes: 2 additions & 2 deletions packages/jwt/src/schemas/valibot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<JwtHeader>(() => true),
v.custom<JwtHeader>((_val): _val is JwtHeader => true),
)

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/jwt/src/schemas/zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down