Back to Blog
·10 min
BI

BreafIO Team

Product & Engineering

How to Implement Sign-In with Ethereum (SIWE) in React & Node.js

Introduction

"Passwordless authentication using Web3 wallets reduces onboarding friction while leveraging cryptographic proof instead of vulnerable credentials."

Traditional Web2 authentication relies on password hashing or third-party OAuth providers. Sign-In with Ethereum (SIWE / EIP-4361) standardizes off-chain signature authentication, allowing users to control their identity directly via cryptographic wallets like MetaMask, Coinbase Wallet, or WalletConnect.

This guide covers building a production-grade Web3 authentication flow using SIWE, Express.js, and React.

1. The SIWE Authentication Lifecycle

A secure SIWE flow prevents replay attacks using server-generated nonces:

React Client → Express.js Server

1. Request Unique Nonce (Store nonce in session)

2. Return Nonce

3. Sign Message with Wallet

4. Post Message & Signature

5. Verify signature & nonce

6. Issue JWT / HTTP-Only Cookie

2. Backend Implementation (Express.js)

First, install the official SIWE SDK: `npm install siwe express-session jsonwebtoken`

import express from 'express';
import { SiweMessage } from 'siwe';
import jwt from 'jsonwebtoken';

const app = express();
app.use(express.json());

// Step 1: Generate & store a cryptographically secure nonce in session
app.get('/api/nonce', (req, res) => {
  req.session.nonce = generateNonce();
  res.setHeader('Content-Type', 'text/plain');
  res.send(req.session.nonce);
});

// Step 2: Verify signature and issue auth token
app.post('/api/verify', async (req, res) => {
  const { message, signature } = req.body;

  try {
    const siweMessage = new SiweMessage(message);
    const fields = await siweMessage.verify({ signature, nonce: req.session.nonce });

    const token = jwt.sign({ address: fields.data.address }, process.env.JWT_SECRET!, { expiresIn: '24h' });
    return res.status(200).json({ success: true, token });
  } catch (error) {
    return res.status(400).json({ success: false, error: 'Invalid signature' });
  }
});

3. Frontend Implementation (React & Ethers.js)

On the client side, construct the standard SIWE message and prompt the user's wallet for a signature:

import { BrowserProvider } from 'ethers';
import { SiweMessage } from 'siwe';

async function signInWithEthereum() {
  const provider = new BrowserProvider(window.ethereum);
  const signer = await provider.getSigner();
  const address = await signer.getAddress();

  // 1. Fetch nonce from server
  const nonceRes = await fetch('/api/nonce');
  const nonce = await nonceRes.text();

  // 2. Create standardized SIWE payload
  const message = new SiweMessage({
    domain: window.location.host,
    address,
    statement: 'Sign in with Ethereum to the application.',
    uri: window.location.origin,
    version: '1',
    chainId: 1,
    nonce
  });

  const preparedMessage = message.prepareMessage();

  // 3. Prompt user signature in wallet
  const signature = await signer.signMessage(preparedMessage);

  // 4. Send to backend for verification
  await fetch('/api/verify', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ message: preparedMessage, signature }),
  });
}

4. Essential Web3 Security Best Practices

Nonce Expiration: Expire nonces after 5 minutes to prevent signature reuse.

Domain Binding: Always verify that message.domain strictly matches your backend's host domain to prevent cross-site signature spoofing.

HTTP-Only Cookies: Store session JWTs in HttpOnly, Secure, SameSite=Strict cookies rather than local storage to eliminate XSS token theft.

Cross-Origin Isolation: Use CORS headers to restrict API access to your frontend domain only. Never accept verification requests from arbitrary origins.

5. Multi-Wallet Support

Modern dApps support multiple wallet providers. Use wagmi + RainbowKit for a drop-in multi-wallet UI that supports MetaMask, Coinbase Wallet, WalletConnect, and dozens more. The SIWE message format is wallet-agnostic — only the provider library changes.

Conclusion

SIWE provides a standardized, secure, and user-friendly alternative to traditional password authentication. The cryptographic proof model eliminates password storage, phishing vulnerabilities, and third-party dependency. For Web3 applications, this is not just an improvement — it is the correct architecture. Start with the nonce → sign → verify → JWT flow, add HTTP-only cookies for session management, and your authentication layer is production-ready.

Ready to Build?

Get started with our production-ready starter kits and ship your project faster.

Browse Starter Kits