Spark Program | [ "JoyID Connect" — Portable Session Auth Across CKB dApps]

Spark Program | [: “JoyID Connect” — Portable Session Auth Across CKB dApps]


1. Project Overview

  • Project name: [ “JoyID Connect”]
  • One-sentence summary: A lightweight SSO layer that lets a single JoyID passkey authenticate a user across multiple independent CKB dApps, with per-app scoped permissions instead of full wallet access.
  • Category: Developer tool / SDK (with a demo dApp trio to prove it out)

2. Team Profile

Core member: Carl — Solo developer, project lead and sole contributor. No collaborators on this application.

Contact:oxdev6 (Oxdev) · GitHub


3. Project Background

JoyID already solved passwordless auth on CKB — native WebAuthn/passkey accounts, no seed phrase. But today every dApp that integrates JoyID treats it as its own login: each app runs its own auth flow against the same passkey, and there’s no shared notion of “this is the same identity, already trusted, across apps.”

This mirrors the pre-SSO era of the web: every site making you log in separately even when you’re using “the same” credential underneath. On CKB specifically, this also means every dApp has to reinvent not just login, but authorization scope — there’s no standard way for a dApp to request “read-only” vs “can sign up to X CKB” from a JoyID identity.

As more CKB dApps adopt JoyID, this fragmentation compounds: each integration is bespoke, and users get no continuity of identity or trust between apps they’ve already authenticated with.


4. Solution

A small SDK + reference implementation that lets a dApp:

  1. Detect if the user already has an active JoyID session (established with another participating dApp)
  2. Request a scoped session grant from the user — e.g., “read-only,” or “can sign transactions up to N CKB” — co-signed by the user’s own passkey, without forcing a full new passkey registration
  3. Verify that grant against the user’s public key already registered with JoyID — not against the requesting dApp — so no individual dApp is ever a trusted issuer for anyone else

User’s view: authenticate once with your JoyID passkey; when you visit a second participating dApp, you’re recognized and asked to approve a scoped permission with one lightweight signature — not a full WebAuthn re-registration, but also not a rubber-stamp “click to trust another app’s word for it.”

Differentiation from plain “use JoyID as your login everywhere”: the scoping mechanism, and — after a security review of the original design (see Section 5) — who is allowed to vouch for the user. This isn’t just federated login (OAuth-style “who are you”), it’s federated authorization (“what can you do here”), and critically, no dApp is ever the identity issuer — only the user’s own passkey and JoyID’s registered public key are.


5. Technical Approach

  • Stack: TypeScript SDK, React/Vite demo frontends, CKB SDK / Lumos for cell construction (future work), JoyID SDK for passkey/WebAuthn integration
  • Repo structure (already scaffolded — see diagram below): a monorepo with packages/sdk (session grant construction/verification, scope enforcement, expiration, public-key signature checks) and packages/demo-suite (three functional mock dApps — DEX, marketplace, lending — currently running against a MockJoyIDAdapter that simulates passkey-backed identity locally, no testnet dependency required to develop against).
  • Architecture, revised after security review:
    • JoyID account = lock script backed by the passkey’s public key (existing JoyID behavior, unchanged)
    • A session grant (off-chain) is a small payload — identity reference, requesting dApp (audience), permission scope, expiry — co-signed by the user’s own passkey, not issued or signed by the requesting dApp
    • Each dApp verifies a grant directly against the user’s public key registered with JoyID, and checks that the grant’s audience field matches its own origin, so a grant issued for dApp B cannot be replayed at dApp C
    • No dApp ever acts as an issuer or identity provider for any other dApp — every dApp is a verifier only

Why this changed from the original design: the first version had the requesting dApp’s server sign session tokens that other dApps would trust — a federation model where compromising one dApp’s backend could forge sessions for every dApp that trusted it. A CKB community reviewer pushed on this directly during the proposal discussion (see forum thread), and the honest answer was that this introduced a new cross-dApp blast radius that doesn’t exist in today’s per-dApp WebAuthn model. Moving the signature to the user’s own passkey removes dApps as trust roots entirely: compromising any single dApp’s backend now leaks nothing that lets it impersonate the user anywhere else, which is no worse than today’s baseline.

Explicit threat model:

  • If a dApp’s server-side key leaks: under the revised design, an attacker gains nothing that lets them forge sessions elsewhere — the dApp was never a signer, only a verifier. This is the property the redesign was for.
  • If the user’s passkey leaks: an attacker can authenticate as the user everywhere JoyID is used, which is already true today under plain JoyID with no SSO layer — a single shared identity is inherently cross-dApp once compromised. This project does not change that baseline.
  • Additional mitigations: per-dApp audience binding (a grant for dApp B is rejected by dApp C), short expiry on grants, and mandatory fresh passkey cosigning for any scope escalation rather than accepting an existing lower-scope grant as a stepping stone to a higher one.

  • Key technical decision, already made: the SDK uses an off-chain, user-cosigned grant model rather than a fully on-chain session registry cell or a dApp-issued-token model. This was the right call for a 1-2 month grant — it’s demonstrable, avoids introducing a new trust root, and is honest about what it is now, with the on-chain registry documented as future work rather than overpromised here.
  • What’s left before this is “real”: the current demo suite proves the logic works end-to-end, but against a simulated identity and the pre-review token design. The core risk this grant retires is two-fold: (1) integration risk — does the flow hold up once MockJoyIDAdapter is replaced with actual WebAuthn calls against JoyID’s testnet, and (2) the redesign — implementing user-cosigned, audience-bound grants in place of dApp-issued tokens.
  • Language: English (repo/docs), Chinese not required per current forum policy.

6. To-Do List (6-week plan)

Starting point: SDK skeleton, an initial (since-revised) session token schema, and all three demo dApps (DEX, marketplace, lending) already exist and run locally against a MockJoyIDAdapter. The plan below covers two things: taking this from “logic proven against a mock” to “working against real JoyID passkeys on testnet,” and implementing the redesigned, user-cosigned grant model that came out of community security review.

  • Week 1 (done — pre-grant): SDK skeleton, initial session token schema, three demo dApps scaffolded and functional against MockJoyIDAdapter.
  • Week 2: Redesign session grants to be user-passkey-cosigned and audience-bound (dApp-specific), replacing the original dApp-issued-token model; update SDK’s create/verify logic accordingly.
  • Week 3: Replace MockJoyIDAdapter with real JoyID SDK integration — actual WebAuthn passkey registration and login against JoyID testnet, wired to the redesigned grant model; finish and expand the SDK unit test suite, including tests asserting a grant issued for one dApp is rejected by another. Milestone 1.
  • Week 4: Deploy dApp A and dApp B to testnet; confirm cross-app session handoff — login on A, recognized grant on B (scoped prompt, one cosigned approval, no re-registration) — works against real infrastructure with the audience-bound grant model. Milestone 2.
  • Week 5: Deploy dApp C to testnet requiring a higher scope; confirm this forces a fresh cosigned grant rather than silently accepting the existing lower-scope one (step-up enforcement).
  • Week 6: Docs (including a short write-up of the threat model and why the design moved away from dApp-issued tokens), demo video, publish SDK to npm, write-up of on-chain-registry future path. Milestone 3.

7. Required Funding & Breakdown

  • Requested total: $1,000 USD (in CKB, or CKB + USDI ≤ 50%)

  • Structure: 20% upfront on approval, remainder released against three verifiable milestones (below), consistent with the program’s standard payment model.

  • Upfront — $200 (20%): released on grant approval.

  • Milestone 1 — Redesigned grant model + real JoyID integration — $250: due end of Week 3. Trigger: session grants are user-passkey-cosigned and audience-bound (not dApp-issued); MockJoyIDAdapter fully replaced with the real JoyID SDK; passkey registration/login working against JoyID testnet. Verification: committee can clone the repo and run npm test to see passing tests, including a test asserting a grant issued for one dApp is rejected when presented to another.

  • Milestone 2 — Cross-app SSO live on testnet — $350: due end of Week 4. Trigger: dApp A and dApp B both deployed on testnet (not just local); login on A, recognized grant on B via one cosigned approval, no re-registration. Verification: live testnet URLs — the committee repeats the flow themselves, no code read required.

  • Milestone 3 — Full demo + docs — $200: due end of Week 6. Trigger: dApp C deployed requiring step-up (a fresh cosigned grant for higher scope), SDK published to npm, demo video published, integration docs including the threat-model write-up complete. Verification: demo video plus the three live URLs plus a README walkthrough.

  • Cost basis: roughly $170/week across a 6-week, part-time solo build — front-loaded slightly toward Milestone 2 since the cross-app session flow (Weeks 3-4) is the technical core the rest depends on.

  • Fallback: if Milestone 2 slips past Week 4, I’ll flag it proactively in the weekly check-in rather than let it surface at final review — the $350 tranche stays gated on the working demo, not the calendar date.


8. Deliverables + How to Verify

Deliverables:

  • Open-source SDK repo (TypeScript), MIT-licensed, published to npm, implementing user-cosigned, audience-bound session grants
  • Three demo dApps (DEX, marketplace, lending mock) deployed on testnet, running against real JoyID passkeys (not the current MockJoyIDAdapter)
  • Short demo video showing: login on dApp A → grant recognized on dApp B via one cosigned approval (not re-registration) → step-up to a higher scope required and separately approved on dApp C
  • README + integration docs, including a short threat-model write-up, for other CKB devs to plug the SDK into their own dApp

How to verify (no code review required):

  • Watch the demo video: one passkey login, one cosigned approval recognized on a second app, a fresh cosigned approval required for the higher-scope third app
  • Visit the three live testnet demo URLs directly and repeat the flow themselves
  • Check the repo’s commit history against the weekly milestones above
  • Run the included test suite (npm test), including the cross-audience rejection test — pass/fail output is self-evident

9. Current State vs. Funded Work

  • Current state: Public repo already live at github.com/oxdev6/JoyID-Connect (MIT-licensed monorepo). packages/sdk has an initial session token model implemented (create, verify, scope enforcement, expiration, signature checks). packages/demo-suite has all three demo dApps (DEX, marketplace, lending) running locally and passing the cross-app session flow — but against a MockJoyIDAdapter rather than real JoyID/WebAuthn, and against the original token design.
  • A note on that original design: during proposal review, a community member correctly identified that the original token model — where a requesting dApp’s own server signed and vouched for session tokens other dApps would trust — introduced a new cross-dApp blast radius (compromising one dApp’s signing key could forge sessions for every dApp that trusted it) that doesn’t exist in today’s per-dApp WebAuthn model. The design has been revised to user-passkey-cosigned, audience-bound grants, where no dApp ever acts as an issuer. See Section 5 for the full threat-model discussion.
  • What this means: the general shape of the SSO/scoped-session flow is already proven end-to-end against a mock. What’s unproven, and what this grant funds, is (1) whether it holds up against real infrastructure — real WebAuthn ceremonies, real signature verification, real network latency — and (2) implementing the revised, security-reviewed grant model in place of the original one.
  • Funded scope: implementing user-cosigned, audience-bound session grants; replacing the mock adapter with real JoyID SDK integration; deploying all three demo dApps to testnet; hardening the SDK test suite (including cross-audience rejection tests); publishing the SDK to npm; and producing docs (including a threat-model write-up) + a demo video. See Section 6 for the week-by-week breakdown.
  • Explicitly out of scope: an on-chain session registry cell (vs. the off-chain, user-cosigned grant model already built) is documented as future work, not part of this grant.

10. CKB Alignment

  • Directly builds on JoyID, a CKB-native primitive, and extends it rather than working around it
  • Uses CKB’s cell model conceptually for session state (even in the off-chain-first version, the design anticipates a future on-chain session cell)
  • Addresses a real ecosystem gap: every JoyID-integrated dApp today re-implements auth independently; this reduces integration friction for future CKB developers, which is a multiplier for ecosystem growth rather than a single-app feature
1 Like

几个问题想请你直接回答:

  1. 这个需求你是怎么发现的? 是自己接入 JoyID 时遇到的真实痛点,还是分析/猜想出来的?
  2. 目前 CKB 上的 dApp 是怎么集成 JoyID 的?你自己实际集成过吗? 如果有,是哪个项目,能不能给我们看一下。
  3. 相比现在"每个 dApp 各自走一遍 JoyID 签名验证登录"的方式,你的方案在安全等级上是持平、还是有妥协?为什么? 请具体说明:现在每次都要重新走 WebAuthn 认证,而你的方案里第二个、第三个 dApp 不用重新认证、只需要用户点一下授权——这个"免重新认证"换来的便利,安全性上到底牺牲了什么,你是怎么补偿回来的? 更具体地说:如果 dApp A 的服务端私钥泄露,攻击者能对你的系统做什么?如果泄露的是用户自己的 passkey,攻击者又能做什么?这两种泄露的影响范围一样吗?
2 Likes

Thanks for pushing on this — these are exactly the right questions, so I’ll answer directly.

1. How did you discover this requirement?

Honestly: analysis, not a personal integration pain point. I looked at how JoyID is currently integrated across dApps — each one runs its own login flow against the same passkey identity — and identified that there’s no shared session/authorization layer between them. I want to be upfront that this came from studying the pattern, not from hitting the wall myself while shipping something. I think it’s a real gap, but I’m not going to pretend it’s a battle scar it isn’t.

2. Have you actually integrated JoyID yourself?

Not in production, no. The current repo (linked in the proposal) runs against a MockJoyIDAdapter that simulates passkey-backed identity locally — that’s exactly what it looks like, a mock, not a real integration. Wiring up the actual JoyID SDK against testnet (real WebAuthn ceremonies, real signature verification) is Milestone 1 of the grant ask, not something already done. I’d rather be clear about that now than have it discovered later.

3. Security comparison — this is the important one.

You’re right to press here, and I want to answer the mechanism, not just reassure you.

Today’s model: every dApp independently runs a WebAuthn ceremony against the user’s passkey. Critically, WebAuthn credentials are origin-bound by design — a credential registered against one relying party isn’t usable by another. Each dApp’s trust in “this is really the user” comes directly from a fresh, user-present cryptographic ceremony, every time. If dApp A is compromised, the blast radius stops at dApp A — it has no way to authenticate as the user anywhere else, because it never held anything that could do that.

My original design broke this property, and I’ll say so plainly rather than defend it: having dApp A issue a session token that dApp B and dApp C trust makes dApp A a trusted issuer for the user’s identity across every participating dApp. That’s a federation model (structurally like OAuth/SAML), and federation models have a well-known failure mode: compromising the issuer doesn’t just compromise the issuer, it compromises everything downstream that trusts it.

So, concretely, to answer your specific question:

  • If dApp A’s server-side signing key leaks: an attacker can forge session tokens claiming to be any user, with any scope, against every dApp that trusts dApp A as an issuer — without ever touching the user’s actual passkey. This is strictly worse than today, where compromising dApp A gets you dApp A and nothing else.
  • If the user’s own passkey leaks: an attacker can authenticate as the user directly, which is already true today under plain JoyID with no SSO layer at all — a single shared identity across dApps already means passkey compromise is cross-dApp by nature. My design doesn’t make this case worse.
  • These are not symmetric, and that’s the honest answer to your question: passkey compromise was already cross-dApp before I touched anything. Server-side issuer-key compromise is a new cross-dApp blast radius that my original design introduced and didn’t need to.

为啥这个帖子的原文版和简体中文版差别这么大?

1 Like

看起来是用作翻译的 LLM 把文本认为成了要回答的内容。这下 LLM In The Loop 了。

cc @terrytai

3 Likes

Checked it directly — the Chinese text isn’t garbled or “answering” anything, it’s a coherent translation of an older revision. I edited the English post after the security discussion earlier in this thread and the Chinese version wasn’t regenerated after that edit. Re-syncing now.

@Fisher @Hanssen 确认是翻译插件的问题,Hanssen 的判断是对的:LLM 把帖子内容当成了要回答的问题。:sweat_smile:

下一个 release 会修复:会优化系统提示词. 翻译前先做语言检测,与原帖相同语言的"翻译"任务根本不再生成,这类问题从机制上消除;存量的错误翻译也会一并清理。很快发布。

感谢反馈, 系统的藏数据会在发布时一并清除. 感谢反馈.

5 Likes

Thanks for digging in and confirming — good catch by @Hanssen. Appreciate the quick fix. English version is still the current/authoritative one in the meantime.

Hi @Carl,

Thank you for submitting the proposal. After a thorough review, the committee has decided to decline funding for JoyID Connect at this stage.

This decision is based on two main considerations:

Practical Ecosystem Fit: The proposed cross-dApp session layer does not align well with current ecosystem needs.

Execution Focus: With your concurrent proposal for the Fiber RGB++ Swap, the committee believes it is best to concentrate development resources on executing that single project successfully before taking on additional initiatives.

We appreciate your dedication to the CKB ecosystem.

Best regards,
Spark Program Committee

cc @Hanssen @yixiu.ckbfans.bit @xingtianchunyan

问题已经修复: [ANN] 翻译插件更新:任意语言按需翻译,并修复译回答问题而非翻译的问题

3 Likes