Can't generate JWKS using 'ES256'

I want to create Postgraphile Schema using createPostGraphileSchema and pass the private key for JWToken verification. However this does not support better's auth default encyption algorithm. Allowed ones according to their comment in interface SignOptions:
/**
* Signature algorithm. Could be one of these values :
* - HS256: HMAC using SHA-256 hash algorithm (default)
* - HS384: HMAC using SHA-384 hash algorithm
* - HS512: HMAC using SHA-512 hash algorithm
* - RS256: RSASSA using SHA-256 hash algorithm
* - RS384: RSASSA using SHA-384 hash algorithm
* - RS512: RSASSA using SHA-512 hash algorithm
* - ES256: ECDSA using P-256 curve and SHA-256 hash algorithm
* - ES384: ECDSA using P-384 curve and SHA-384 hash algorithm
* - ES512: ECDSA using P-521 curve and SHA-512 hash algorithm
* - none: No digital signature or MAC value included
*/
/**
* Signature algorithm. Could be one of these values :
* - HS256: HMAC using SHA-256 hash algorithm (default)
* - HS384: HMAC using SHA-384 hash algorithm
* - HS512: HMAC using SHA-512 hash algorithm
* - RS256: RSASSA using SHA-256 hash algorithm
* - RS384: RSASSA using SHA-384 hash algorithm
* - RS512: RSASSA using SHA-512 hash algorithm
* - ES256: ECDSA using P-256 curve and SHA-256 hash algorithm
* - ES384: ECDSA using P-384 curve and SHA-384 hash algorithm
* - ES512: ECDSA using P-521 curve and SHA-512 hash algorithm
* - none: No digital signature or MAC value included
*/
12 Replies
LightTab2
LightTab2OP2mo ago
Current approach: I just changed jwks key pair algorithm to 'ES256' But when I try to access http://localhost/api/auth/jwks, I get:
# SERVER_ERROR: TypeError: non-extractable CryptoKey cannot be exported as a JWK
at keyToJWK (file:///[PROJECT_PATH]/node_modules/jose/dist/webapi/lib/key_to_jwk.js:23:15)
at exportJWK (file:///[PROJECT_PATH]/node_modules/jose/dist/webapi/key/export.js:10:12)
at file:///[PROJECT_PATH]/node_modules/better-auth/dist/plugins/jwt/index.mjs:212:41
at async internalHandler ([PROJECT_PATH]\node_modules\better-call\src\endpoint.ts:332:20)
at async api.<computed> (file:///[PROJECT_PATH]/node_modules/better-auth/dist/api/index.mjs:516:22)
at async processRequest ([PROJECT_PATH]\node_modules\better-call\src\router.ts:179:22)
at async handler ([PROJECT_PATH]\node_modules\better-call\src\router.ts:200:16)
at async eval ([PROJECT_PATH]\node_modules\@tanstack\start-server-core\src\createStartHandler.ts:435:22)
at async next ([PROJECT_PATH]\node_modules\@tanstack\start-server-core\src\createStartHandler.ts:451:20)
at async handleServerRoutes ([PROJECT_PATH]\node_modules\@tanstack\start-server-core\src\createStartHandler.ts:409:17)
at async eval ([PROJECT_PATH]\node_modules\@tanstack\start-server-core\src\createStartHandler.ts:224:48)
at async startRequestResolver ([PROJECT_PATH]\node_modules\@tanstack\start-server-core\src\createStartHandler.ts:129:24)
at async file:///[PROJECT_PATH]/node_modules/@tanstack/start-plugin-core/dist/esm/dev-server-plugin/plugin.js:45:30
# SERVER_ERROR: TypeError: non-extractable CryptoKey cannot be exported as a JWK
at keyToJWK (file:///[PROJECT_PATH]/node_modules/jose/dist/webapi/lib/key_to_jwk.js:23:15)
at exportJWK (file:///[PROJECT_PATH]/node_modules/jose/dist/webapi/key/export.js:10:12)
at file:///[PROJECT_PATH]/node_modules/better-auth/dist/plugins/jwt/index.mjs:212:41
at async internalHandler ([PROJECT_PATH]\node_modules\better-call\src\endpoint.ts:332:20)
at async api.<computed> (file:///[PROJECT_PATH]/node_modules/better-auth/dist/api/index.mjs:516:22)
at async processRequest ([PROJECT_PATH]\node_modules\better-call\src\router.ts:179:22)
at async handler ([PROJECT_PATH]\node_modules\better-call\src\router.ts:200:16)
at async eval ([PROJECT_PATH]\node_modules\@tanstack\start-server-core\src\createStartHandler.ts:435:22)
at async next ([PROJECT_PATH]\node_modules\@tanstack\start-server-core\src\createStartHandler.ts:451:20)
at async handleServerRoutes ([PROJECT_PATH]\node_modules\@tanstack\start-server-core\src\createStartHandler.ts:409:17)
at async eval ([PROJECT_PATH]\node_modules\@tanstack\start-server-core\src\createStartHandler.ts:224:48)
at async startRequestResolver ([PROJECT_PATH]\node_modules\@tanstack\start-server-core\src\createStartHandler.ts:129:24)
at async file:///[PROJECT_PATH]/node_modules/@tanstack/start-plugin-core/dist/esm/dev-server-plugin/plugin.js:45:30
jwt plugin config:
jwt(
{
jwt:
{
expirationTime: '5min'
},
jwks:
{
keyPairConfig:
{
alg: 'ES256'
},
disablePrivateKeyEncryption: true
}
})
jwt(
{
jwt:
{
expirationTime: '5min'
},
jwks:
{
keyPairConfig:
{
alg: 'ES256'
},
disablePrivateKeyEncryption: true
}
})
Is this a bug? Is there a better solution to this problem?
# select * from jwks;
id | publicKey | privateKey | createdAt
----+-----------+------------+-----------
(0 rows)
# select * from jwks;
id | publicKey | privateKey | createdAt
----+-----------+------------+-----------
(0 rows)
the jwks table is empty, key is not generated In case this is relevant... My dependencies:
"devDependencies":
{
"@eslint/css": "^0.9.0",
"@eslint/js": "^9.30.0",
"@eslint/json": "^0.12.0",
"@types/node": "^24.0.7",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"autoprefixer": "^10.4.21",
"eslint": "^9.30.0",
"eslint-plugin-react": "^7.37.5",
"globals": "^16.2.0",
"postcss": "^8.5.6",
"postcss-cli": "^11.0.1",
"typescript": "^5.8.3",
"typescript-eslint": "^8.35.0",
"vite-tsconfig-paths": "^5.1.4"
},
"dependencies":
{
"@tanstack/react-query": "^5.81.5",
"@tanstack/react-router": "^1.122.0",
"@tanstack/react-router-devtools": "^1.122.0",
"@tanstack/react-router-with-query": "^1.122.0",
"@tanstack/react-start": "^1.122.1",
"@tanstack/react-table": "^8.21.3",
"@tanstack/react-virtual": "^3.13.12",
"@vitejs/plugin-react": "^4.6.0",
"async-mutex": "^0.5.0",
"axios": "^1.10.0",
"better-auth": "^1.2.12",
"i18next": "^25.2.1",
"i18next-browser-languagedetector": "^8.2.0",
"jose": "^6.0.11",
"postgraphile": "^4.14.1",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-google-recaptcha-v3": "^1.11.0",
"react-i18next": "^15.5.3",
"react-textarea-autosize": "^8.5.9",
"socket.io-client": "^4.8.1",
"vite": "^7.0.0",
"zod": "^3.25.67"
}
"devDependencies":
{
"@eslint/css": "^0.9.0",
"@eslint/js": "^9.30.0",
"@eslint/json": "^0.12.0",
"@types/node": "^24.0.7",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"autoprefixer": "^10.4.21",
"eslint": "^9.30.0",
"eslint-plugin-react": "^7.37.5",
"globals": "^16.2.0",
"postcss": "^8.5.6",
"postcss-cli": "^11.0.1",
"typescript": "^5.8.3",
"typescript-eslint": "^8.35.0",
"vite-tsconfig-paths": "^5.1.4"
},
"dependencies":
{
"@tanstack/react-query": "^5.81.5",
"@tanstack/react-router": "^1.122.0",
"@tanstack/react-router-devtools": "^1.122.0",
"@tanstack/react-router-with-query": "^1.122.0",
"@tanstack/react-start": "^1.122.1",
"@tanstack/react-table": "^8.21.3",
"@tanstack/react-virtual": "^3.13.12",
"@vitejs/plugin-react": "^4.6.0",
"async-mutex": "^0.5.0",
"axios": "^1.10.0",
"better-auth": "^1.2.12",
"i18next": "^25.2.1",
"i18next-browser-languagedetector": "^8.2.0",
"jose": "^6.0.11",
"postgraphile": "^4.14.1",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-google-recaptcha-v3": "^1.11.0",
"react-i18next": "^15.5.3",
"react-textarea-autosize": "^8.5.9",
"socket.io-client": "^4.8.1",
"vite": "^7.0.0",
"zod": "^3.25.67"
}
> node -v
v24.3.0
> npm -v
11.4.2
> pnpm -v
10.12.4
> node -v
v24.3.0
> npm -v
11.4.2
> pnpm -v
10.12.4
I can try to cook up some minimal example if this isn't some obvious mistake on my side
Solution
D▲VID
D▲VID2mo ago
I am getting the same error Im also using ES256
Ted
Ted2mo ago
Same issue
LightTab2
LightTab2OP2mo ago
Fix is probably not coming soon on the main branch, but u can switch to v1.3 branch:
pnpm install https://github.com/better-auth/better-auth#v1.3
pnpm install https://github.com/better-auth/better-auth#v1.3
adam-beck
adam-beck2mo ago
I mentioned this in the ticket as well, but even with "better-auth": "1.3.0-beta.9" I'm getting the following error
SERVER_ERROR: JOSENotSupported: Invalid or unsupported JWK "alg" (Algorithm) Parameter value
// auth.ts

...
jwks: {
keyPairConfig: {
alg: "RS256",
},
},
...
// auth.ts

...
jwks: {
keyPairConfig: {
alg: "RS256",
},
},
...
Okay after deleting the items in the jwks table, I get
TypeError: non-extractable CryptoKey cannot be exported as a JWK
LightTab2
LightTab2OP2mo ago
Let me update and see if it happens to me too Yes this is when it tries to use old jwk, so you did the right move It works for me, can't reproduce the error. I believe my tests would catch a break like that... @adam-beck I'll need more info on your error, maybe I can help What did you call? Did you confirm its only happening for "RS256" and not other algorithms? Mb, I reproduced it eventually, I'll look how it escaped testing first It's RSA issue on endpoint There is some typechecking it fails, becaues the .kty is always send as 'EC' for some reason so it's like the algorithms are working, but sending config which one to use does not
adam-beck
adam-beck2mo ago
I'm not ignoring you but I'll be AFK most of today. I do know I didn't try any other algorithm. Lol you're going to quickly go over my head. I'm so bad with authentication.
LightTab2
LightTab2OP2mo ago
Nah, it's just I spend some time debugging this code, I'm 4 months experience into webdev at all, don't get impostor syndrome dude lol I'll try to fix it today and post local fix if I am successful I wasn't on point @adam-beck quickest fix for now is to go back to 1.3.0-beta.8 Someone made PR that broke mine and somehow it passes all the new tests
LightTab2
LightTab2OP2mo ago
GitHub
fix(jwt): Reimplement fix that allows to generate JWKS with other a...
I&amp;#39;m afraid the test cases I&amp;#39;ve made in previous PR were insufficient. PR #2755 reintroduced issue #3218. This is because tests checked internals, but didn&amp;#39;t test the functio...
LightTab2
LightTab2OP2mo ago
@adam-beck v1.3 was released and it has the fix
adam-beck
adam-beck2mo ago
Oh wow really! That's awesome. Great work!

Did you find this page helpful?