Skip to content

feat: implement SEP-10 web authentication alongside existing SIWS - #114

Open
collinsezedike wants to merge 2 commits into
blockchain-maxis:mainfrom
collinsezedike:feat/sep10-web-auth-65
Open

feat: implement SEP-10 web authentication alongside existing SIWS#114
collinsezedike wants to merge 2 commits into
blockchain-maxis:mainfrom
collinsezedike:feat/sep10-web-auth-65

Conversation

@collinsezedike

Copy link
Copy Markdown
Contributor

Summary

  • Add a spec-compliant SEP-10 (Stellar Web Authentication) endpoint at GET/POST /api/auth/sep10 — a single URL for both steps, matching what stellar.toml's WEB_AUTH_ENDPOINT advertises and what generic SEP-10 clients expect (splitting challenge/verify across two paths would break anything using a standard SEP-10 library)
  • Challenge/verify mechanics (timebounds, home_domain/web_auth_domain Manage Data operations, signature checks) are delegated to @stellar/stellar-sdk's WebAuth module (the reference implementation), in lib/sep10.ts, rather than hand-rolled — this is exactly where subtle spec violations creep in (transposed domain fields, infinite timebounds, accepting a caller-supplied home_domain blindly, etc.)
  • On success, mints both the spec's HS256 JWT ({ token }) and this app's existing session cookie, so tRPC's account.* procedures work unchanged regardless of which auth flow a caller used
  • The endpoint is reachable cross-origin (Access-Control-Allow-Origin: * + OPTIONS), since the entire point of SEP-10 is interoperability with wallets/tooling that aren't this app's own frontend — unlike the existing same-origin-protected endpoints
  • Added /.well-known/stellar.toml so external SEP-10 clients can actually discover WEB_AUTH_ENDPOINT, SIGNING_KEY, and NETWORK_PASSPHRASE
  • The existing custom Sign-In-With-Stellar flow (lib/auth.ts, /api/auth/{challenge,verify}) is untouched and still fully functional — verified with a manual smoke test end to end
  • The app's own frontend (lib/wallet.ts's signIn()) now goes through SEP-10 (signTransaction instead of signMessage) to complete the migration for our own UI

Test plan

  • lib/sep10.test.ts: challenge/signature round-trip, rejects unsigned challenge, rejects wrong signer, rejects a caller-supplied home_domain that doesn't match this service, rejects expired timebounds, rejects a challenge built against a different server key, JWT round-trip + tamper/garbage rejection
  • pnpm --filter @signet/web test (39/39), typecheck, lint all pass
  • Manual smoke test against a production build (next build && next start): challenge → sign → verify → JWT + session cookie issued; unsigned challenge rejected (401); stellar.toml served with correct WEB_AUTH_ENDPOINT/SIGNING_KEY; legacy /api/auth/{challenge,verify} flow still works unchanged

Closes #65

@netlify

netlify Bot commented Jul 29, 2026

Copy link
Copy Markdown

Deploy Preview for stellar-signet ready!

Name Link
🔨 Latest commit f4b0e93
🔍 Latest deploy log https://app.netlify.com/projects/stellar-signet/deploys/6a710d90e7325b0008a30319
😎 Deploy Preview https://deploy-preview-114--stellar-signet.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

@collinsezedike is attempting to deploy a commit to the blockchainmaxis-8449's projects Team on Vercel.

A member of the Team first needs to authorize it.

@collinsezedike
collinsezedike force-pushed the feat/sep10-web-auth-65 branch from 1e9a9f8 to ea7f075 Compare July 29, 2026 13:48
@drips-wave

drips-wave Bot commented Jul 29, 2026

Copy link
Copy Markdown

@collinsezedike Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@blockchain-maxis

Copy link
Copy Markdown
Owner

This is careful work and the security reasoning is sound — delegating the challenge/verify mechanics to the SDK's WebAuth rather than hand-rolling them, rejecting a caller-supplied home_domain that isn't ours, Allow-Origin: * deliberately paired with no Allow-Credentials and a SameSite=lax cookie, and timingSafeEqual with a length guard in verifyJwt. No conflicts either.

I'm holding the merge on one deployment prerequisite rather than on anything in the diff.

Blocking: SEP10_SIGNING_SECRET has to exist before this lands

getServerKeypair() throws when the secret is unset under NODE_ENV=production:

if (process.env.NODE_ENV === 'production') {
  throw new Error('SEP10_SIGNING_SECRET must be set in production');
}

That's the right call on its own — an ephemeral keypair would make the SIGNING_KEY in stellar.toml wrong after every restart. But lib/wallet.ts also moves the app's own signIn() onto this endpoint, so the two together mean that on the next production deploy without that variable set:

  • /api/auth/sep10 500s, and sign-in is broken for everyone, not just SEP-10 clients
  • /.well-known/stellar.toml 500s

So this needs the secret provisioned in the deployment environment first. I'll merge as soon as that's done — nothing for you to change here.

Non-blocking: the acceptance criteria asked for spec vectors

Issue #65 asks for "tests against the spec's vectors". lib/sep10.test.ts covers the round trip plus a good set of negative paths — unsigned challenge, wrong signer, mismatched home_domain, expired timebounds, challenge built against a different server key, JWT tamper/garbage. That's genuinely useful coverage, but it's all self-generated: it verifies this implementation against itself, so a shared misreading of the spec on both sides would pass.

Adding a couple of fixed XDR challenge transactions from SEP-10's own test vectors, asserted against readChallengeTx, would close that gap. Happy to take it as a follow-up rather than hold this PR for it.

Worth confirming, not changing

signIn() moves users from a signMessage prompt to a signTransaction prompt. That's inherent to SEP-10 and the server-side SIWS endpoints are untouched, so this is a UI-visible change rather than a compatibility break — flagging it so it isn't a surprise.

One question while this is open: is SEP10_WEB_AUTH_DOMAIN expected to differ from NEXT_PUBLIC_ROOT_DOMAIN in any planned deployment? Single-domain is what the code assumes by default and that looks right, but it's worth stating in .env.example if a split is ever intended.

@collinsezedike

Copy link
Copy Markdown
Contributor Author

Thank you for the review, @blockchain-maxis. Addressed both non-blocking points in f4b0e93:

  • Added 3 fixed SEP-10 vectors to lib/sep10.test.ts: a hardcoded known-good signed challenge XDR (generated once with a fixed server/client keypair pair, well outside our own buildChallenge code path), run through WebAuth.readChallengeTx/verifyChallengeTxSigners directly, plus rejection cases for the wrong server key and a stripped client signature.
  • SEP10_WEB_AUTH_DOMAIN is documented in .env.example now. To answer directly: no split is planned right now, single-domain is the intended default, and the code already falls back to NEXT_PUBLIC_ROOT_DOMAIN when it's unset. The variable is there for if that ever changes.

Leaving SEP10_SIGNING_SECRET provisioning to you as discussed.

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.

Implement SEP-10 web authentication

2 participants