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.
Related Template
Try this production-ready starter kit to build your project faster.
Connected Wallets
MetaMask
12.45 ETH
WalletConnect
2,450 USDC
Coinbase Wallet
Not connected
Supported Chains
Ethereum
Polygon
Arbitrum
Optimism
Base
Avalanche
Web3 Auth Kit
Wallet authentication for any dApp
Ready to Build?
Get started with our production-ready starter kits and ship your project faster.
Browse Starter Kits