Implement Secure Web3 Authentication
Implement Web3 wallet authentication with SIWE, EIP-191/712 signature verification, nonce management, and multi-chain support.
Maintainer of this project? Claim this page to edit the listing.
1.0.0Add to Favorites
Why it matters
Integrate robust wallet-based authentication into your dApp. This asset provides expert guidance and code examples for secure signature verification, multi-wallet support, and decentralized identity management across various blockchain networks.
Outcomes
What it gets done
Implement signature-based authentication using EIP-191, EIP-712, and SIWE.
Integrate MetaMask and WalletConnect for seamless wallet connections.
Develop secure nonce management and session handling for replay attack prevention.
Provide code examples for multi-chain support and error handling.
Install
Add it to your toolbox
Run in your project directory:
curl -fsSL https://spark.entire.vc/get/vb-web3-authentication | bash Overview
Web3 Authentication Expert
A Web3 authentication skill covering wallet connection, EIP-191/712 signature verification, SIWE flows, nonce management, and multi-chain support. Use it when implementing wallet-based authentication for a dApp that needs standards-compliant signature verification.
What it does
This skill implements Web3 wallet-based authentication using cryptographic signatures instead of passwords. It covers message signing standards (EIP-191 Ethereum Signed Message, EIP-712 typed structured data, SIWE - Sign-In with Ethereum) and security fundamentals (never requesting private keys, using nonces to prevent replay attacks, verifying signatures server-side). It implements MetaMask wallet connection via eth_requestAccounts, multi-wallet support through web3-react connectors (Injected, WalletConnect), client-side EIP-191 message signing with server-side verification via ethers.utils.verifyMessage, EIP-712 typed data signing with a domain/types/value structure, a full SIWE flow (frontend message construction and signing, backend validation issuing a JWT), cryptographically secure nonce generation and Redis-backed storage with expiration, JWT-based session middleware, multi-chain configuration and validation, wallet error handling for common MetaMask error codes (4001 for user-rejected connection, -32002 for an already-pending connection request, 4902 for an unsupported network), and network-switching logic using wallet_switchEthereumChain/wallet_addEthereumChain.
When to use - and when NOT to
Use this skill when implementing wallet-based Web3 authentication - connecting to MetaMask or supporting multiple wallet providers, implementing EIP-191 or EIP-712 signature signing and verification, building a full SIWE authentication flow with nonce validation and JWT issuance, managing secure nonces with expiration to prevent replay attacks, validating supported chains, or handling wallet connection errors and network switching.
It does not cover smart contract development or on-chain transaction logic - it is focused specifically on off-chain wallet-based authentication and session management.
Inputs and outputs
Inputs are typically a dApp's frontend wallet connector and backend session requirements. Outputs include authentication code, for example a full SIWE backend verification handler:
const verifyAuth = async (req, res) => {
const { message, signature } = req.body;
const siweMessage = new SiweMessage(message);
const fields = await siweMessage.validate(signature);
const isValidNonce = await validateNonce(fields.nonce);
if (!isValidNonce) {
return res.status(400).json({ error: 'Invalid nonce' });
}
const token = jwt.sign({ address: fields.address, chainId: fields.chainId }, process.env.JWT_SECRET, { expiresIn: '24h' });
res.json({ token, user: { address: fields.address } });
};
Other outputs include a MetaMask connection function, EIP-191/EIP-712 signing and verification code, a Redis-backed nonce generation/validation pair, a JWT auth middleware, multi-chain configuration and validation, wallet error-code handling, and network-switching logic.
Multi-wallet configuration pins supported chain IDs explicitly, for example [1, 4, 56, 137] for the injected MetaMask connector, and configures a WalletConnect connector with its own RPC URL and bridge endpoint.
Who it's for
dApp developers implementing wallet-based authentication who need standards-compliant (SIWE/EIP-712) signature verification and secure nonce/session management rather than a custom, unverified auth scheme.
Source README
You are an expert in Web3 authentication systems, specializing in wallet-based authentication, signature verification, cryptographic security, and decentralized identity management. You have deep knowledge of authentication patterns across different blockchain networks and wallet providers.
Core Authentication Principles
Signature-Based Authentication
Web3 authentication relies on cryptographic signatures rather than passwords. Users prove ownership of a wallet by signing messages with their private key.
Message Signing Standards
- EIP-191: Ethereum Signed Message Standard
- EIP-712: Typed structured data hashing and signing
- SIWE (Sign-In with Ethereum): RFC-compliant authentication standard
Security Fundamentals
- Never request private keys or seed phrases
- Use nonces to prevent replay attacks
- Implement proper signature verification
- Validate wallet ownership through message signing
Wallet Connection Implementation
MetaMask Integration
// Check for MetaMask availability
const connectWallet = async () => {
if (typeof window.ethereum !== 'undefined') {
try {
// Request account access
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
// Get network information
const chainId = await window.ethereum.request({
method: 'eth_chainId'
});
return { address: accounts[0], chainId };
} catch (error) {
console.error('User rejected connection:', error);
throw error;
}
} else {
throw new Error('MetaMask not installed');
}
};
Multi-Wallet Support
import { WalletConnectConnector } from '@web3-react/walletconnect-connector';
import { InjectedConnector } from '@web3-react/injected-connector';
const connectors = {
metamask: new InjectedConnector({
supportedChainIds: [1, 4, 56, 137]
}),
walletconnect: new WalletConnectConnector({
rpc: { 1: process.env.REACT_APP_RPC_URL },
bridge: 'https://bridge.walletconnect.org',
qrcode: true
})
};
Signature Verification Patterns
EIP-191 Message Signing
import { ethers } from 'ethers';
// Client-side signing
const signMessage = async (message, signer) => {
const signature = await signer.signMessage(message);
return signature;
};
// Server-side verification
const verifySignature = (message, signature, expectedAddress) => {
try {
const recoveredAddress = ethers.utils.verifyMessage(message, signature);
return recoveredAddress.toLowerCase() === expectedAddress.toLowerCase();
} catch (error) {
return false;
}
};
EIP-712 Typed Data Signing
const domain = {
name: 'MyApp',
version: '1',
chainId: 1,
verifyingContract: '0x...' // Optional
};
const types = {
Authentication: [
{ name: 'user', type: 'address' },
{ name: 'nonce', type: 'uint256' },
{ name: 'timestamp', type: 'uint256' }
]
};
const value = {
user: '0x...',
nonce: 123456,
timestamp: Math.floor(Date.now() / 1000)
};
const signature = await signer._signTypedData(domain, types, value);
SIWE (Sign-In with Ethereum) Implementation
Frontend Integration
import { SiweMessage } from 'siwe';
const createSiweMessage = (address, statement, nonce) => {
const message = new SiweMessage({
domain: window.location.host,
address,
statement,
uri: window.location.origin,
version: '1',
chainId: 1,
nonce,
issuedAt: new Date().toISOString()
});
return message.prepareMessage();
};
const authenticateWithSiwe = async (signer) => {
// Get nonce from server
const nonceResponse = await fetch('/api/nonce');
const { nonce } = await nonceResponse.json();
const address = await signer.getAddress();
const message = createSiweMessage(address, 'Sign in to MyApp', nonce);
const signature = await signer.signMessage(message);
// Verify with server
const authResponse = await fetch('/api/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message, signature })
});
return authResponse.json();
};
Backend Verification
import { SiweMessage } from 'siwe';
import jwt from 'jsonwebtoken';
const verifyAuth = async (req, res) => {
try {
const { message, signature } = req.body;
const siweMessage = new SiweMessage(message);
// Verify signature and message validity
const fields = await siweMessage.validate(signature);
// Check nonce (prevent replay attacks)
const isValidNonce = await validateNonce(fields.nonce);
if (!isValidNonce) {
return res.status(400).json({ error: 'Invalid nonce' });
}
// Generate JWT token
const token = jwt.sign(
{ address: fields.address, chainId: fields.chainId },
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);
res.json({ token, user: { address: fields.address } });
} catch (error) {
res.status(401).json({ error: 'Authentication failed' });
}
};
Security Best Practices
Nonce Management
// Generate cryptographically secure nonces
const generateNonce = () => {
return crypto.randomBytes(16).toString('hex');
};
// Store nonces with expiration (Redis example)
const storeNonce = async (nonce) => {
await redis.setex(`nonce:${nonce}`, 300, 'valid'); // 5 min expiry
};
const validateNonce = async (nonce) => {
const result = await redis.del(`nonce:${nonce}`);
return result === 1; // Returns true if nonce existed and was deleted
};
Session Management
const authMiddleware = async (req, res, next) => {
try {
const token = req.headers.authorization?.replace('Bearer ', '');
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Optional: Verify wallet still owns the address
req.user = { address: decoded.address, chainId: decoded.chainId };
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
};
Multi-Chain Support
Chain-Specific Configuration
const CHAIN_CONFIG = {
1: { name: 'Ethereum', rpc: 'https://mainnet.infura.io/v3/...' },
56: { name: 'BSC', rpc: 'https://bsc-dataseed1.binance.org/' },
137: { name: 'Polygon', rpc: 'https://polygon-rpc.com/' },
43114: { name: 'Avalanche', rpc: 'https://api.avax.network/ext/bc/C/rpc' }
};
const validateChain = (chainId, allowedChains = [1, 56, 137]) => {
return allowedChains.includes(parseInt(chainId));
};
Error Handling and UX
Comprehensive Error Management
const handleWalletError = (error) => {
if (error.code === 4001) {
return 'User rejected the connection request';
} else if (error.code === -32002) {
return 'Connection request already pending';
} else if (error.code === 4902) {
return 'Unsupported network';
}
return 'Failed to connect wallet';
};
Network Switching
const switchToNetwork = async (chainId) => {
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: `0x${chainId.toString(16)}` }]
});
} catch (error) {
if (error.code === 4902) {
// Network not added, add it
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [CHAIN_CONFIG[chainId]]
});
}
}
};
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.