diff --git a/examples/with-next-siwe-iron-session/src/pages/api/verify.ts b/examples/with-next-siwe-iron-session/src/pages/api/verify.ts index 17aeffed34..96ce52eee7 100644 --- a/examples/with-next-siwe-iron-session/src/pages/api/verify.ts +++ b/examples/with-next-siwe-iron-session/src/pages/api/verify.ts @@ -13,6 +13,17 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { const { message, signature } = req.body; const siweMessage = parseSiweMessage(message) as SiweMessage; + // Bind SIWE domain to this app host (parity with with-next-siwe-next-auth). + // Without this check, a signature minted for an attacker-controlled domain + // can be replayed against this verify endpoint under a stolen/own nonce. + const expectedHost = req.headers.host; + if (!expectedHost || siweMessage.domain !== expectedHost) { + return res.status(422).json({ message: 'Invalid domain.' }); + } + + if (siweMessage.nonce !== req.session.nonce) + return res.status(422).json({ message: 'Invalid nonce.' }); + const success = await publicClient.verifyMessage({ address: siweMessage.address, message, @@ -21,9 +32,6 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { if (!success) throw new Error('Invalid signature.'); - if (siweMessage.nonce !== req.session.nonce) - return res.status(422).json({ message: 'Invalid nonce.' }); - req.session.siwe = siweMessage; await req.session.save(); res.json({ ok: true });