Documentation Index
Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
Use this file to discover all available pages before exploring further.
Try it Live
Run SIWE examples in the interactive playground
Usage Patterns
Production patterns for SIWE authentication.Complete Authentication Flow
Frontend Implementation
// 1. Request nonce from backend
async function startAuth() {
const response = await fetch('/auth/start', {
method: 'POST',
});
const { nonce } = await response.json();
return nonce;
}
// 2. Create SIWE message
async function createMessage(address: string, nonce: string) {
const chainId = await ethereum.request({ method: 'eth_chainId' });
return Siwe.create({
domain: window.location.host,
address: Address(address),
uri: window.location.origin,
chainId: Number(chainId),
statement: 'Sign in to Example App',
nonce,
});
}
// 3. Sign message
async function signMessage(message: BrandedMessage) {
const text = Siwe.format(message);
const signature = await ethereum.request({
method: 'personal_sign',
params: [text, message.address],
});
return signature;
}
// 4. Complete auth flow
async function authenticate() {
try {
// Get user address
const [address] = await ethereum.request({
method: 'eth_requestAccounts',
});
// Start authentication
const nonce = await startAuth();
// Create and sign message
const message = await createMessage(address, nonce);
const signature = await signMessage(message);
// Send to backend
const response = await fetch('/auth/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: Siwe.format(message),
signature,
}),
});
if (!response.ok) {
throw new Error('Authentication failed');
}
const { token } = await response.json();
localStorage.setItem('authToken', token);
return { success: true };
} catch (err) {
console.error('Auth failed:', err);
return { success: false, error: err.message };
}
}
Backend Implementation
import { Redis } from 'ioredis';
import { Siwe, Address } from 'tevm';
const redis = new Redis();
// 1. Start authentication endpoint
app.post('/auth/start', async (req, res) => {
try {
// Generate nonce
const nonce = Siwe.generateNonce();
const expiresAt = Date.now() + 300000; // 5 minutes
// Store nonce
await redis.set(
`nonce:${nonce}`,
JSON.stringify({
createdAt: Date.now(),
expiresAt,
}),
'EX',
300
);
res.json({ nonce });
} catch (err) {
res.status(500).json({ error: 'Failed to start authentication' });
}
});
// 2. Verify authentication endpoint
app.post('/auth/verify', async (req, res) => {
try {
const { message: messageText, signature: signatureHex } = req.body;
// Parse message
const message = Siwe.parse(messageText);
// Verify domain
if (message.domain !== req.hostname) {
return res.status(400).json({ error: 'Domain mismatch' });
}
// Verify nonce
const nonceData = await redis.get(`nonce:${message.nonce}`);
if (!nonceData) {
return res.status(400).json({ error: 'Invalid or expired nonce' });
}
const { expiresAt } = JSON.parse(nonceData);
if (Date.now() > expiresAt) {
return res.status(400).json({ error: 'Nonce expired' });
}
// Consume nonce (single use)
await redis.del(`nonce:${message.nonce}`);
// Validate message
const validationResult = Siwe.validate(message);
if (!validationResult.valid) {
return res.status(400).json({ error: validationResult.error.message });
}
// Verify signature
const signature = hexToBytes(signatureHex);
const verifyResult = Siwe.verifyMessage(message, signature);
if (!verifyResult.valid) {
return res.status(401).json({ error: verifyResult.error.message });
}
// Create session
const sessionToken = generateSessionToken();
await redis.set(
`session:${sessionToken}`,
JSON.stringify({
address: Address.toHex(message.address),
chainId: message.chainId,
createdAt: Date.now(),
}),
'EX',
86400 // 24 hours
);
res.json({ token: sessionToken });
} catch (err) {
console.error('Auth verification failed:', err);
res.status(400).json({ error: 'Authentication failed' });
}
});
Session Management
Expiring Sessions
const message = Siwe.create({
domain: "example.com",
address: userAddress,
uri: "https://example.com",
chainId: 1,
expirationTime: new Date(Date.now() + 3600000).toISOString(), // 1 hour
});
// Middleware to check session validity
app.use((req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'No token' });
}
redis.get(`session:${token}`).then(data => {
if (!data) {
return res.status(401).json({ error: 'Invalid session' });
}
const session = JSON.parse(data);
req.session = session;
next();
});
});
Sliding Sessions
// Extend session on each request
app.use(async (req, res, next) => {
if (req.session) {
const token = req.headers.authorization?.replace('Bearer ', '');
await redis.expire(`session:${token}`, 86400); // Reset to 24 hours
}
next();
});
Multi-Chain Authentication
const supportedChains = [1, 137, 42161]; // Ethereum, Polygon, Arbitrum
async function authenticateMultiChain(address: string) {
const nonce = await startAuth();
const messages = await Promise.all(
supportedChains.map(async (chainId) => {
return Siwe.create({
domain: window.location.host,
address: Address(address),
uri: window.location.origin,
chainId,
nonce,
});
})
);
// User selects chain and signs
const selectedChainId = await promptChainSelection();
const message = messages.find(m => m.chainId === selectedChainId);
const signature = await signMessage(message);
}
Resource-Based Authorization
const ADMIN_RESOURCES = [
"https://example.com/api/admin/users",
"https://example.com/api/admin/settings",
];
function createAuthMessage(address: string, role: 'admin' | 'user') {
const resources = role === 'admin' ? ADMIN_RESOURCES : USER_RESOURCES;
return Siwe.create({
domain: "example.com",
address,
uri: "https://example.com",
chainId: 1,
statement: `Grant ${role} access`,
resources,
});
}
// Middleware
app.use('/api/admin/*', (req, res, next) => {
const requestedResource = `https://example.com${req.path}`;
if (!hasResourceAccess(req.session.message, requestedResource)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
});
Security Best Practices
Clock Skew Handling
const CLOCK_SKEW_MS = 30000; // 30 seconds
function validateWithSkew(message: BrandedMessage): ValidationResult {
const now = new Date(Date.now() - CLOCK_SKEW_MS);
return Siwe.validate(message, { now });
}
HTTPS Only
// Middleware to enforce HTTPS
app.use((req, res, next) => {
if (!req.secure && process.env.NODE_ENV === 'production') {
return res.status(403).json({ error: 'HTTPS required' });
}
next();
});
Audit Logging
async function logAuthAttempt(
address: string,
success: boolean,
reason?: string
) {
await db.auditLog.create({
type: 'auth_attempt',
address,
success,
reason,
timestamp: new Date(),
ip: req.ip,
userAgent: req.get('user-agent'),
});
}
See Also
- Siwe.create - Create messages
- Siwe.verify - Verify signatures
- Validation - Validate messages
- EIP-4361 - Specification

