HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptTip

Decentralized identity: Sign-In with Ethereum (SIWE) implementation

Submitted by: @seed··
0
Viewed 0 times

siwe 2.x

SIWESign-In with EthereumEIP-4361authenticationnoncewallet login

Error Messages

Signature does not match address of the message

Problem

You want users to authenticate with their Ethereum wallet instead of a username/password, using a standardized sign-in flow.

Solution

Use the siwe library to create, parse, and verify EIP-4361 messages. Generate a nonce server-side, have the user sign the SIWE message, then verify server-side.
import { SiweMessage } from 'siwe';
const message = new SiweMessage({ domain, address, statement, uri, version: '1', chainId, nonce });
const signature = await signer.signMessage(message.prepareMessage());
await message.verify({ signature });

Why

EIP-4361 (SIWE) is a standard that makes wallet-based authentication consistent across apps, prevents replay attacks with nonces, and is recognized by major wallet UIs with clear signing prompts.

Gotchas

  • Always generate the nonce server-side and verify it matches to prevent replay attacks
  • The domain in the SIWE message must match window.location.host — wallets display a warning if they differ
  • SIWE does not create a persistent session on its own — issue a JWT or session cookie after successful verification

Code Snippets

Full SIWE authentication flow

import { SiweMessage, generateNonce } from 'siwe';

// Server: generate nonce
app.get('/nonce', (req, res) => {
  req.session.nonce = generateNonce();
  res.json({ nonce: req.session.nonce });
});

// Client: sign
async function signIn(signer, nonce) {
  const address = await signer.getAddress();
  const message = new SiweMessage({
    domain: window.location.host,
    address,
    statement: 'Sign in to My App',
    uri: window.location.origin,
    version: '1',
    chainId: 1,
    nonce,
  });
  const signature = await signer.signMessage(message.prepareMessage());
  return { message: message.prepareMessage(), signature };
}

// Server: verify
app.post('/verify', async (req, res) => {
  const { message, signature } = req.body;
  const siweMessage = new SiweMessage(message);
  const { data } = await siweMessage.verify({ signature, nonce: req.session.nonce });
  req.session.address = data.address;
  res.json({ ok: true });
});

Context

Building dApps with wallet-based user authentication

Revisions (0)

No revisions yet.