From 0538bdee7232478dc24a1fb7f85e142037634d9e Mon Sep 17 00:00:00 2001 From: kozemyrpol <167280170+kozemyrpol@users.noreply.github.com> Date: Fri, 29 May 2026 06:00:14 +0700 Subject: [PATCH] Harden SIWE authentication examples --- .../guides/authenticate-users.mdx | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/docs/base-account/guides/authenticate-users.mdx b/docs/base-account/guides/authenticate-users.mdx index 7a8332200..07db70234 100644 --- a/docs/base-account/guides/authenticate-users.mdx +++ b/docs/base-account/guides/authenticate-users.mdx @@ -147,13 +147,31 @@ try { ```ts Backend (Viem) import { createPublicClient, http } from 'viem'; import { base } from 'viem/chains'; +import { parseSiweMessage } from 'viem/siwe'; const client = createPublicClient({ chain: base, transport: http() }); +const expectedDomain = 'yourapp.com'; +const nonces = new Set(); // Populate this from your /auth/nonce route. export async function verifySig(req, res) { const { address, message, signature } = req.body; - const valid = await client.verifyMessage({ address, message, signature }); + const { nonce } = parseSiweMessage(message); + + // Check the nonce against your server-side store before accepting it. + if (!nonce || !nonces.has(nonce)) { + return res.status(400).json({ error: 'Invalid or reused nonce' }); + } + + const valid = await client.verifySiweMessage({ + address, + message, + signature, + domain: expectedDomain, + nonce, + }); if (!valid) return res.status(401).json({ error: 'Invalid signature' }); + + nonces.delete(nonce); // create session / JWT res.json({ ok: true }); } @@ -185,6 +203,7 @@ import crypto from "crypto"; import express from "express"; import { createPublicClient, http } from "viem"; import { base } from "viem/chains"; +import { parseSiweMessage } from "viem/siwe"; const app = express(); app.use(express.json()); @@ -199,19 +218,33 @@ app.get("/auth/nonce", (_, res) => { }); const client = createPublicClient({ chain: base, transport: http() }); +const expectedDomain = process.env.SIWE_DOMAIN ?? "localhost:3000"; app.post("/auth/verify", async (req, res) => { const { address, message, signature } = req.body; - // 1. Check nonce hasn\'t been reused - const nonce = message.match(/at (\w{32})$/)?.[1]; - if (!nonce || !nonces.delete(nonce)) { + // 1. Parse the SIWE message and check that the nonce exists server-side + let nonce: string | undefined; + try { + nonce = parseSiweMessage(message).nonce; + } catch { + return res.status(400).json({ error: "Invalid SIWE message" }); + } + + if (!nonce || !nonces.has(nonce)) { return res.status(400).json({ error: "Invalid or reused nonce" }); } - // 2. Verify signature - const valid = await client.verifyMessage({ address, message, signature }); + // 2. Verify the signature and the expected SIWE fields + const valid = await client.verifySiweMessage({ + address, + message, + signature, + domain: expectedDomain, + nonce, + }); if (!valid) return res.status(401).json({ error: "Invalid signature" }); + nonces.delete(nonce); // 3. Create session / JWT here res.json({ ok: true });