diff --git a/docs/base-account/guides/authenticate-users.mdx b/docs/base-account/guides/authenticate-users.mdx
index 7a8332200..65ce02e6d 100644
--- a/docs/base-account/guides/authenticate-users.mdx
+++ b/docs/base-account/guides/authenticate-users.mdx
@@ -147,12 +147,22 @@ try {
```ts Backend (Viem)
import { createPublicClient, http } from 'viem';
import { base } from 'viem/chains';
+import { verifySiweMessage } from 'viem/siwe';
+
+// The domain your app is served from. Anything signed for another
+// domain must not be accepted here.
+const APP_DOMAIN = 'yourapp.com';
const client = createPublicClient({ chain: base, transport: http() });
export async function verifySig(req, res) {
const { address, message, signature } = req.body;
- const valid = await client.verifyMessage({ address, message, signature });
+ const valid = await verifySiweMessage(client, {
+ address,
+ message,
+ signature,
+ domain: APP_DOMAIN,
+ });
if (!valid) return res.status(401).json({ error: 'Invalid signature' });
// create session / JWT
res.json({ ok: true });
@@ -178,6 +188,14 @@ export async function verifySig(req, res) {
regardless of where it originated.
+
+ Always check the `domain` field of the signed message against your own domain.
+ A signature is valid for whichever domain it was signed for, so a signature a
+ user produced on another site is cryptographically valid on yours too.
+ `verifySiweMessage` performs this check when you pass `domain`; a bare
+ `verifyMessage` call does not, and accepts the signature.
+
+
### Example Express Server
```ts title="server/auth.ts" expandable
@@ -185,6 +203,7 @@ import crypto from "crypto";
import express from "express";
import { createPublicClient, http } from "viem";
import { base } from "viem/chains";
+import { parseSiweMessage, verifySiweMessage } from "viem/siwe";
const app = express();
app.use(express.json());
@@ -192,6 +211,10 @@ app.use(express.json());
// Simple in-memory nonce store (swap for Redis or DB in production)
const nonces = new Set();
+// The domain your app is served from. Anything signed for another
+// domain must not be accepted here.
+const APP_DOMAIN = "yourapp.com";
+
app.get("/auth/nonce", (_, res) => {
const nonce = crypto.randomBytes(16).toString("hex");
nonces.add(nonce);
@@ -203,14 +226,19 @@ const client = createPublicClient({ chain: base, transport: http() });
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];
+ // 1. Check this server issued the nonce and hasn't seen it before
+ const { nonce } = parseSiweMessage(message);
if (!nonce || !nonces.delete(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 bind it to your domain
+ const valid = await verifySiweMessage(client, {
+ address,
+ message,
+ signature,
+ domain: APP_DOMAIN,
+ });
if (!valid) return res.status(401).json({ error: "Invalid signature" });
// 3. Create session / JWT here