[GH-ISSUE #3218] Cannot generate JWKS with other algorithm than the default one #9525

Closed
opened 2026-04-13 05:01:19 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @LightTab2 on GitHub (Jun 29, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/3218

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Clone my minimal example repository
  2. pnpm -i
  3. pnpm dev
  4. Try to access http://localhost:3000/api/auth/jwks

Current vs. Expected behavior

Expected:

JSON response that would look something like this:

{"keys":[{"kty":"EC","x":"KEY_x","y":"KEY_y","crv":"P-256","alg":"ES256"}]}

Current:

SERVER_ERROR:  TypeError: non-extractable CryptoKey cannot be exported as a JWK
    at keyToJWK (D:\better-auth-jwtbug-minimalexample\node_modules\jose\dist\webapi\lib\key_to_jwk.js:23:15)
    at Object.exportJWK (D:\better-auth-jwtbug-minimalexample\node_modules\jose\dist\webapi\key\export.js:10:12)
    at D:\better-auth-jwtbug-minimalexample\node_modules\better-auth\dist\plugins\jwt\index.cjs:214:46
    at async internalHandler (D:\better-auth-jwtbug-minimalexample\node_modules\better-call\src\endpoint.ts:332:20)
    at async api.<computed> (D:\better-auth-jwtbug-minimalexample\node_modules\better-auth\dist\api\index.cjs:520:22)
    at async processRequest (D:\better-auth-jwtbug-minimalexample\node_modules\better-call\src\router.ts:179:22)
    at async handler (D:\better-auth-jwtbug-minimalexample\node_modules\better-call\src\router.ts:200:16)
    at async <anonymous> (D:\better-auth-jwtbug-minimalexample\node_modules\better-call\src\adapters\node\index.ts:11:20)

What version of Better Auth are you using?

1.2.12

Provide environment information

- OS: Windows 10 IoT Enterprise LTSC 21H2 build 19044.5965
- Node: v24.3.0
- NPM:  11.4.2
- PNPM: 10.12.4
First encountered when working with TanStack Start.

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
import { jwt } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: 
    [
        jwt(
        {
            jwt: 
            {
                expirationTime: '5min',
            },
            jwks:
            {
                keyPairConfig: 
                {
                    alg: 'ES256'
                }
            }
        }), 
    ]
});

Additional context

Tested all algorithms, only default works and only if it is not specified explicitly, that is: providing any keyPairConfig breaks it.

Originally created by @LightTab2 on GitHub (Jun 29, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/3218 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Clone my [minimal example repository](https://github.com/LightTab2/better-auth-jwtbug-minimalexample) 2. `pnpm -i` 3. `pnpm dev` 4. Try to access `http://localhost:3000/api/auth/jwks` ### Current vs. Expected behavior ## Expected: JSON response that would look something like this: ```json {"keys":[{"kty":"EC","x":"KEY_x","y":"KEY_y","crv":"P-256","alg":"ES256"}]} ``` ## Current: ``` SERVER_ERROR: TypeError: non-extractable CryptoKey cannot be exported as a JWK at keyToJWK (D:\better-auth-jwtbug-minimalexample\node_modules\jose\dist\webapi\lib\key_to_jwk.js:23:15) at Object.exportJWK (D:\better-auth-jwtbug-minimalexample\node_modules\jose\dist\webapi\key\export.js:10:12) at D:\better-auth-jwtbug-minimalexample\node_modules\better-auth\dist\plugins\jwt\index.cjs:214:46 at async internalHandler (D:\better-auth-jwtbug-minimalexample\node_modules\better-call\src\endpoint.ts:332:20) at async api.<computed> (D:\better-auth-jwtbug-minimalexample\node_modules\better-auth\dist\api\index.cjs:520:22) at async processRequest (D:\better-auth-jwtbug-minimalexample\node_modules\better-call\src\router.ts:179:22) at async handler (D:\better-auth-jwtbug-minimalexample\node_modules\better-call\src\router.ts:200:16) at async <anonymous> (D:\better-auth-jwtbug-minimalexample\node_modules\better-call\src\adapters\node\index.ts:11:20) ``` ### What version of Better Auth are you using? 1.2.12 ### Provide environment information ```bash - OS: Windows 10 IoT Enterprise LTSC 21H2 build 19044.5965 - Node: v24.3.0 - NPM: 11.4.2 - PNPM: 10.12.4 First encountered when working with TanStack Start. ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" import { jwt } from "better-auth/plugins"; export const auth = betterAuth({ plugins: [ jwt( { jwt: { expirationTime: '5min', }, jwks: { keyPairConfig: { alg: 'ES256' } } }), ] }); ``` ### Additional context Tested all algorithms, only default works and only if it is not specified explicitly, that is: providing any `keyPairConfig` breaks it.
GiteaMirror added the lockedbug labels 2026-04-13 05:01:19 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Jun 29, 2025):

Hi @LightTab2! I'm Dosu and I’m helping the better-auth team.

This happens because when you specify a custom algorithm in keyPairConfig, the generated key is not marked as extractable by default, which is required for exporting it as a JWK. The default config sets extractable: true, but when you override with your own config (like { alg: 'ES256' }), that property is missing, so the key can't be exported and you get the error.

To fix this, explicitly set extractable: true in your keyPairConfig:

jwks: {
  keyPairConfig: {
    alg: 'ES256',
    extractable: true,
  }
}

This will allow the key to be exported and the JWKS endpoint to work as expected. This is a subtle bug in how the config is merged—if you provide any custom config, you need to include extractable: true yourself, otherwise the key will be non-extractable and JWKS export will fail. See the relevant code logic here.

There’s no documentation or support thread that calls this out directly, but this workaround should resolve your issue.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Join Discord Share on X

<!-- gh-comment-id:3016824347 --> @dosubot[bot] commented on GitHub (Jun 29, 2025): <!-- Greeting --> Hi @LightTab2! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> This happens because when you specify a custom algorithm in `keyPairConfig`, the generated key is not marked as extractable by default, which is required for exporting it as a JWK. The default config sets `extractable: true`, but when you override with your own config (like `{ alg: 'ES256' }`), that property is missing, so the key can't be exported and you get the error. To fix this, explicitly set `extractable: true` in your `keyPairConfig`: ```typescript jwks: { keyPairConfig: { alg: 'ES256', extractable: true, } } ``` This will allow the key to be exported and the JWKS endpoint to work as expected. This is a subtle bug in how the config is merged—if you provide any custom config, you need to include `extractable: true` yourself, otherwise the key will be non-extractable and JWKS export will fail. See the relevant code logic [here](https://github.com/better-auth/better-auth/blob/7c4719ed77941bc1a685dc16550be10ad980f3df/packages/better-auth/src/plugins/jwt/index.ts). There’s no documentation or support thread that calls this out directly, but this workaround should resolve your issue. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/2925b746-dab3-4378-b4cf-31bcb4bff831?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/2925b746-dab3-4378-b4cf-31bcb4bff831?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/2925b746-dab3-4378-b4cf-31bcb4bff831?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/2925b746-dab3-4378-b4cf-31bcb4bff831?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/2925b746-dab3-4378-b4cf-31bcb4bff831?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/2925b746-dab3-4378-b4cf-31bcb4bff831?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/2925b746-dab3-4378-b4cf-31bcb4bff831?feedback_type=other)</sup>&nbsp;&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/3218)
Author
Owner

@LightTab2 commented on GitHub (Jun 29, 2025):

type JWKOptions = {
    alg: "EdDSA";
    crv?: "Ed25519" | "Ed448";
} | {
    alg: "ES256";
    crv?: never;
} | {
    alg: "RS256";
    modulusLength?: number;
} | {
    alg: "PS256";
    modulusLength?: number;
} | {
    alg: "ECDH-ES";
    crv?: "P-256" | "P-384" | "P-521";
} | {
    alg: "ES512";
    crv?: never;
};

Does not allow for this:

jwks: {
  keyPairConfig: {
    alg: 'ES256',
    extractable: true,
  }
}

I have requested #3219 to fix this issue.

<!-- gh-comment-id:3016827051 --> @LightTab2 commented on GitHub (Jun 29, 2025): ```js type JWKOptions = { alg: "EdDSA"; crv?: "Ed25519" | "Ed448"; } | { alg: "ES256"; crv?: never; } | { alg: "RS256"; modulusLength?: number; } | { alg: "PS256"; modulusLength?: number; } | { alg: "ECDH-ES"; crv?: "P-256" | "P-384" | "P-521"; } | { alg: "ES512"; crv?: never; }; ``` Does not allow for this: ```js jwks: { keyPairConfig: { alg: 'ES256', extractable: true, } } ``` I have requested #3219 to fix this issue.
Author
Owner

@ahirner commented on GitHub (Jun 29, 2025):

Probably the same as #2407.

<!-- gh-comment-id:3017079197 --> @ahirner commented on GitHub (Jun 29, 2025): Probably the same as #2407.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#9525