chore: fix jwt not working with different algs

This commit is contained in:
Bereket Engida
2025-07-19 12:58:12 -07:00
parent dd7a158d47
commit 9b66fe6815
3 changed files with 47 additions and 25 deletions
+22 -2
View File
@@ -7,7 +7,7 @@ import type {
import { type Jwk, schema } from "./schema";
import { getJwksAdapter } from "./adapter";
import { getJwtToken } from "./sign";
import { exportJWK, generateKeyPair } from "jose";
import { exportJWK, generateKeyPair, type JWK } from "jose";
import {
createAuthEndpoint,
createAuthMiddleware,
@@ -119,6 +119,26 @@ export interface JwtOptions {
schema?: InferOptionSchema<typeof schema>;
}
export async function generateExportedKeyPair(
options?: JwtOptions,
): Promise<{ publicWebKey: JWK; privateWebKey: JWK }> {
const { alg, ...cfg } = options?.jwks?.keyPairConfig ?? {
alg: "EdDSA",
crv: "Ed25519",
};
const keyPairConfig = {
...cfg,
extractable: true,
};
const { publicKey, privateKey } = await generateKeyPair(alg, keyPairConfig);
const publicWebKey = await exportJWK(publicKey);
const privateWebKey = await exportJWK(privateKey);
return { publicWebKey, privateWebKey };
}
export const jwt = (options?: JwtOptions) => {
return {
id: "jwt",
@@ -221,8 +241,8 @@ export const jwt = (options?: JwtOptions) => {
crv: "Ed25519",
};
const keyPairConfig = {
extractable: true,
...cfg,
extractable: true,
};
const { publicKey, privateKey } = await generateKeyPair(
@@ -2,7 +2,7 @@ import { describe, expect } from "vitest";
import { getTestInstance } from "../../test-utils/test-instance";
import { createAuthClient } from "../../client";
import { jwtClient } from "./client";
import { jwt } from "./index";
import { generateExportedKeyPair, jwt, type JwtOptions } from "./index";
import { importJWK, jwtVerify } from "jose";
type JWKOptions =
@@ -228,18 +228,17 @@ describe("jwt", async (it) => {
for (const algorithm of algorithmsToTest) {
const expectedOutcome = algorithm.expectedOutcome;
for (let disablePrivateKeyEncryption of [false, true]) {
const jwtOptions: JwtOptions = {
jwks: {
keyPairConfig: {
...algorithm.keyPairConfig,
},
disablePrivateKeyEncryption: disablePrivateKeyEncryption,
},
};
try {
const { auth, signInWithTestUser } = await getTestInstance({
plugins: [
jwt({
jwks: {
keyPairConfig: {
...algorithm.keyPairConfig,
},
disablePrivateKeyEncryption: disablePrivateKeyEncryption,
},
}),
],
plugins: [jwt(jwtOptions)],
logger: {
level: "error",
},
@@ -269,6 +268,17 @@ describe("jwt", async (it) => {
expect(jwks?.keys.at(0)?.n).toHaveLength(expectedOutcome.length);
});
it(`${alg} algorithm${enc}: Endpoint "/token" can extract valid keys`, async () => {
const { publicWebKey, privateWebKey } =
await generateExportedKeyPair(jwtOptions);
for (const key of [publicWebKey, privateWebKey]) {
expect(key.kty).toBe(expectedOutcome.ec);
if (key.x) expect(key.x).toHaveLength(expectedOutcome.length);
if (key.y) expect(key.y).toHaveLength(expectedOutcome.length);
if (key.n) expect(key.n).toHaveLength(expectedOutcome.length);
}
});
const client = createAuthClient({
plugins: [jwtClient()],
baseURL: "http://localhost:3000/api/auth",
+4 -12
View File
@@ -1,8 +1,8 @@
import { importJWK, exportJWK, generateKeyPair, SignJWT } from "jose";
import { importJWK, SignJWT } from "jose";
import type { GenericEndpointContext } from "../../types";
import { BetterAuthError } from "../../error";
import { symmetricDecrypt, symmetricEncrypt } from "../../crypto";
import type { JwtOptions } from ".";
import { generateExportedKeyPair, type JwtOptions } from ".";
import type { Jwk } from "./schema";
import { getJwksAdapter } from "./adapter";
@@ -17,16 +17,8 @@ export async function getJwtToken(
!options?.jwks?.disablePrivateKeyEncryption;
if (key === undefined) {
const { publicKey, privateKey } = await generateKeyPair(
options?.jwks?.keyPairConfig?.alg ?? "EdDSA",
options?.jwks?.keyPairConfig ?? {
crv: "Ed25519",
extractable: true,
},
);
const publicWebKey = await exportJWK(publicKey);
const privateWebKey = await exportJWK(privateKey);
const { publicWebKey, privateWebKey } =
await generateExportedKeyPair(options);
const stringifiedPrivateWebKey = JSON.stringify(privateWebKey);
let jwk: Partial<Jwk> = {