Skip to content

feat: Next.js SIWX multichain own-server example (EVM + Bitcoin + Tron)#207

Open
rtomas wants to merge 1 commit into
mainfrom
rtomas/nextjs-siwx-multichain-server
Open

feat: Next.js SIWX multichain own-server example (EVM + Bitcoin + Tron)#207
rtomas wants to merge 1 commit into
mainfrom
rtomas/nextjs-siwx-multichain-server

Conversation

@rtomas

@rtomas rtomas commented May 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds nextjs/next-siwx-multichain-own-server: a Next.js example that implements the SIWX SIWXConfig interface directly (not DefaultSIWX), so nonce issuance, signature verification, and session storage all live in this app's own API routes — no client-side verifiers run.

  • /api/siwx/{nonce,verify,sessions,revoke} with HMAC-signed stateless nonces and a jose JWT in an httpOnly cookie.
  • Per-chain server verifiers: viem (eip155), bip322-js (bip122), tronweb verifyMessageV2 (tron).
  • ServerSIWX.signMessage() works around AppKit 1.8.20's TronAdapter.signMessage() stub by calling window.tronWeb.trx.signMessageV2 directly for Tron; other chains delegate to ConnectionController like DefaultSigner.

Test plan

  • cp .env.local.example .env.local and set SIWX_SECRET
  • pnpm install && pnpm dev
  • Connect EVM wallet, sign SIWX message, verify "Server says: Authenticated"
  • Connect Bitcoin wallet, sign, verify
  • Connect Tron (TronLink) wallet, sign, verify
  • Disconnect / refresh and confirm session clears

… Tron)

Adds nextjs/next-siwx-multichain-own-server: a Next.js example that
implements the SIWXConfig interface directly (not DefaultSIWX), so signature
verification and session storage live entirely in this app's API routes.

- /api/siwx/{nonce,verify,sessions,revoke} with HMAC-signed stateless nonces
  and a jose JWT in an httpOnly cookie.
- Per-chain server verifiers: viem for eip155, bip322-js for bip122,
  tronweb verifyMessageV2 for tron.
- Custom ServerSIWX.signMessage() bypasses the AppKit Tron adapter's stub
  (returns '') by calling window.tronWeb.trx.signMessageV2 directly; other
  chains delegate to ConnectionController as in DefaultSigner.
- Reuses InformalMessenger from @reown/appkit-siwx for SIWE-style messages.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@vercel

vercel Bot commented May 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
appkit-web-examples-javascript-bitcoin Ready Ready Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-javascript-ethers Ready Ready Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-javascript-solana Ready Ready Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-javascript-wagmi Ready Ready Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-next-bitcoin Ready Ready Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-next-ethers Ready Ready Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-next-multichain Ready Ready Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-next-solana Ready Ready Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-next-wagmi Ready Ready Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-react-bitcoin Ready Ready Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-react-core-up Ready Ready Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-react-ethers Ready Ready Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-react-ethers5 Ready Ready Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-react-multichain Ready Ready Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-react-solana Ready Ready Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-react-wagmi Building Building Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-vue-bitcoin Ready Ready Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-vue-ethers Ready Ready Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-vue-multichain Ready Ready Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-vue-solana Ready Ready Preview, Comment May 26, 2026 4:01pm
appkit-web-examples-vue-wagmi Ready Ready Preview, Comment May 26, 2026 4:01pm
react-core-universal-connector Ready Ready Preview, Comment May 26, 2026 4:01pm

Request Review

@claude

claude Bot commented May 26, 2026

Copy link
Copy Markdown

Claude finished @rtomas's task in 2m 45s —— View job


PR Review

  • Read changed files
  • Analyze security, code quality, and correctness
  • Run license compliance subagent
  • Run breaking changes subagent
  • Run data classification subagent
  • Post final review

License compliance: ✅ All MIT/Apache-2.0 — no issues
Breaking changes: ✅ None — all files are new additions

Found 6 issue(s)

Issue 1: Hardcoded real projectId makes the if (!projectId) guard dead code

ID: dcl-config-hardcoded-projectid-a3f1
File: nextjs/next-siwx-multichain-own-server/src/config/index.ts:16-18
Severity: CRITICAL
Category: security / data_classification

Context:

  • Pattern: process.env.NEXT_PUBLIC_PROJECT_ID || "b56e18d47c72ab683b10814fe9495694" — real credential as fallback, so the if (!projectId) throw on line 18 is unreachable
  • Risk: Live API key hardcoded in git history; developers copying this example silently inherit it
  • Impact: Quota abuse / account suspension for the key owner; contradicts the best-practice this example is meant to teach
  • Trigger: Immediate — key is public in the repository

Recommendation:

export const projectId = process.env.NEXT_PUBLIC_PROJECT_ID;
if (!projectId) throw new Error("NEXT_PUBLIC_PROJECT_ID is not defined");

Fix this →


Issue 2: .env.local.example contains a real key instead of a placeholder

ID: dcl-env-example-hardcoded-projectid-c7b2
File: nextjs/next-siwx-multichain-own-server/.env.local.example:1
Severity: CRITICAL
Category: security / data_classification

Context:

  • Pattern: Template file has NEXT_PUBLIC_PROJECT_ID=b56e18d47c72ab683b10814fe9495694 — a live value, not a placeholder
  • Risk: Developers who cp .env.local.example .env.local (as instructed in the test plan) inherit and use this credential
  • Impact: Same as Issue 1 — key abuse, plus false sense of "using my own key"

Recommendation:

NEXT_PUBLIC_PROJECT_ID=your_project_id_here
SIWX_SECRET=replace-me-with-a-strong-random-secret

Fix this →


Issue 3: setSessions loop overwrites cookie on each iteration — only last session survives

ID: serversiwx-setsessions-cookie-overwrite-b91c
File: nextjs/next-siwx-multichain-own-server/src/siwx/ServerSIWX.ts:135-137
Severity: HIGH
Category: code_quality / correctness

Context:

  • Pattern: setSessions calls addSession in a for loop; each addSession calls POST /api/siwx/verify which calls setSessionCookie, overwriting the single siwx_session cookie
  • Risk: If AppKit ever calls setSessions([s1, s2]), only s2's cookie is set; session for s1 is silently lost
  • Impact: Multi-session scenarios (e.g. switching chains) could drop a valid session without error
  • Trigger: Any setSessions call with more than one session entry

Recommendation: Since the session store holds a single cookie, either assert that setSessions receives at most one entry, or document the single-session constraint explicitly:

async setSessions(sessions: SIWXSession[]): Promise<void> {
  if (sessions.length === 0) {
    await fetch("/api/siwx/revoke", { method: "POST", credentials: "include" });
    return;
  }
  // Single-session cookie store: persist only the most recent session
  await this.addSession(sessions[sessions.length - 1]);
}

Issue 4: nonce.ts reads SIWX_SECRET directly, bypassing the length validation in getSecret()

ID: nonce-secret-bypass-validation-e4a7
File: nextjs/next-siwx-multichain-own-server/src/lib/server/nonce.ts:39-42
Severity: MEDIUM
Category: security

Context:

  • Pattern: signNonce reads process.env.SIWX_SECRET directly and only checks for truthiness; getSecret() in env.ts enforces length >= 16nonce.ts never imports or calls it
  • Risk: A secret shorter than 16 chars passes the nonce HMAC check but would fail the JWT check; inconsistent security thresholds
  • Impact: Weak secrets accepted for nonce signing

Recommendation:

import { getSecret } from "./env";  // reuse the same validated secret

function signNonce(...): string {
  const secret = Buffer.from(getSecret()).toString("hex");
  return createHmac("sha256", secret).update(`${random}|${exp}|${chainId}|${address}`).digest("hex");
}

Or at minimum mirror the length guard.


Issue 5: Sessions endpoint returns raw signature — unnecessary exposure

ID: dcl-sessions-route-sig-exposure-f4e9
File: nextjs/next-siwx-multichain-own-server/src/app/api/siwx/sessions/route.ts:29-40
Severity: MEDIUM
Category: security / data_classification

Context:

  • Pattern: Response includes message and signature fields; only accountAddress and chainId are needed by AppKit to determine connection state
  • Risk: Signature proves wallet ownership — returning it creates an extra exposure surface. A CSRF or XSS vector could harvest proof-of-ownership tokens replayable against other SIWE-accepting services
  • Impact: Credential replay against third-party SIWE services with weak nonce checks

Recommendation: Return only the identity data AppKit needs:

sessions: [{ data: { accountAddress: session.address, chainId: session.chainId } }]

Issue 6: console.debug in addSession logs session metadata to stdout in all environments

ID: serversiwx-debug-logging-c2f1
File: nextjs/next-siwx-multichain-own-server/src/siwx/ServerSIWX.ts:80-85
Severity: LOW
Category: code_quality

Context:

  • Pattern: console.debug("[ServerSIWX.addSession] sending", { dataKeys, data, ... }) runs unconditionally — console.debug is not suppressed by default in Node/browser production builds
  • Risk: Session metadata (key names, message/signature lengths) emitted to server logs for every sign-in
  • Impact: Log noise; in stricter environments this could be flagged by log-scraping security tools

Recommendation: Remove the debug call, or guard with process.env.NODE_ENV !== "production".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant