Skip to content

plugin-auth: JWT plugin's EdDSA keygen breaks login on hosts without Ed25519 (WebContainer/StackBlitz) #3585

Description

@os-zhuang

Summary

On any host whose WebCrypto lacks Ed25519, every authenticated request 500s as soon as the OIDC provider is enabled — which is the default whenever the MCP server is on. Reproduced on StackBlitz/WebContainer (Node 22.22.3) with @objectstack/* 16.1.0.

Symptom

Sign-in succeeds, then the first /api/v1/auth/get-session returns 500:

[Better Auth]: OperationError DOMException [OperationError]: The operation failed for an operation-specific reason
    at Object.cfrgGenerateKey (builtins.js:74:3727)
    at async SubtleCrypto.generateKey (builtins.js:90:10600) {
  [cause]: TypeError: o.run is not a function
      at Module.generateKeyPair (jose/dist/webapi/key/generate_key_pair.js:111:26)
      at generateExportedKeyPair (better-auth/dist/plugins/jwt/utils.mjs:42:48)
      at Module.createJwk (better-auth/dist/plugins/jwt/utils.mjs:61:58)
      at resolveSigningKey (better-auth/dist/plugins/jwt/sign.mjs:76:26)
}
[AuthManager] better-auth returned error: 500

GET /api/v1/auth/jwks reproduces it with no credentials at all — that route generates the key pair on first use.

Root cause

  1. plugin-auth registers better-auth's JWT plugin as jwt({ schema: buildJwtPluginSchema() }) — no jwks.keyPairConfig, so better-auth's default EdDSA (Ed25519) applies.
  2. WebContainer's WebCrypto has no Ed25519, so cfrgGenerateKey throws.
  3. The plugin's after hook on /get-session signs a set-auth-jwt header for every session, so the failure hits normal cookie login — not just OAuth clients.
  4. It is on by default in a plain dev server: resolveOidcProviderEnabled(pluginConfig) falls back to isMcpServerEnabled(), which defaults to true.

So an app that never asked for OIDC still gets an unusable login on such a host, and the error message points at better-auth rather than at the algorithm choice.

Workaround

OS_OIDC_PROVIDER_ENABLED=false drops the plugin and login works (verified end to end: sign-in/email 200, get-session 200, jwks 404). Apps can't do better than this today because the algorithm isn't configurable through AuthPluginConfig.

Suggested fix

Any of, roughly in order of preference:

  1. Pass an algorithm the host supports — e.g. probe crypto.subtle.generateKey({name:'Ed25519'}, …) once at startup and fall back to ES256 when it throws.
  2. Expose jwks.keyPairConfig through AuthPluginConfig so an app can pin ES256/RS256.
  3. At minimum, catch the keygen failure and degrade the OIDC provider (there is already getDegradedAuthFeatures) instead of 500ing the whole session path — plus an error that names Ed25519.

Environment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions