Skip to content

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

Closed

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

Activity

  1. claude commented on Aug 4, 2026

    @claude
    Contributor

    认领:PM 循环第 1 轮(identity 车道)
    会话:session_015W6nhsDrz6zWQc8je12a1t
    分支:claude/issue-3585-jwt-eddsa-fallback
    Worktree:objectstack-issue-3585
    域:domain:identity
    文件面:packages/plugins/plugin-auth/**(预计 auth-manager.ts、auth-schema-config.ts 一带 + 测试 + changeset)。⛔ packages/spec/** 零改动 —— 若方案 2(把 keyPairConfig 暴露进 AuthPluginConfig)要求改 spec 契约面,即触发 needs_decision 停下,不自行定契约形状


    Generated by Claude Code

  2. claude commented on Aug 4, 2026

    @claude
    Contributor

    PM 验收(第 1 轮,identity 车道):ACCEPT — PR #5044,CI 绿后转 ready 入队

    复核逐条(对 GitHub 实况):

    • 前提在 rc.2 上重新核实过:better-auth 1.7.0-rc.2 的 utils.mjs 默认仍是 {alg:'EdDSA',crv:'Ed25519'},jwt({schema}) 注册点仍在 auth-manager.ts:2175 —— 不是照 issue 快照施工。
    • 范围与裁定一致:方案 1+3(能力探针 + 响亮降级),探针用 jose 的同款 {name:'Ed25519'} 描述符而非宿主嗅探;packages/spec/** 零改动 ✓;文件面全部落 plugin-auth ✓(6 文件含 changeset)。
    • 升级路径那条警告被证实是承重的:resolveSigningKey 会回退到 getLatestKey()(不看算法),所以仅配 keyPairConfig 只救新部署 —— 存量 sys_jwks 里的 EdDSA 键仍会被选中并死在 importJWK。dev 的解法:仅在无 Ed25519 的宿主上安装 better-auth 文档化的 adapter.getJwks 密钥环 seam,隐藏(不删除)导入不了的键,铸新 ES256 键收敛;正常宿主完全走原生读路径。行为变更边界清晰,行有回迁测试(宿主恢复 Ed25519 后 EdDSA 行复原)。
    • 测试证据 ✓:699/699(37 新);关键强度在 auth-manager.jwt-eddsa-fallback.test.ts —— patch 的是 WebCrypto 本身而不是自家探针(探针 stub 会漏掉「选中了导不进的键」这条真实 500 路径),真管线跑通升级收敛全程。开发中试出的两个真 bug(裸 undefined 只把 500 上移一帧,因 runAfterHooks 无守卫读 result.headers)已各自 pin 住。4 条上游 tripwire 钉住 better-auth 的默认算法/hook 形状/seam —— 升 rc.3+ 时会响。
    • docs drift 公告核过,不构成待办:content/docs/ 全文零处宣称 EdDSA/Ed25519(反查 OIDC 正常命中),无文档因此过时。

    CI 目前 21 检 0 红、11 在跑;绿后转 ready 入合并队列,本单随 PR 关闭。


    Generated by Claude Code

  3. added a commit that references this issue on Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions