Try it Live
Run SIWE examples in the interactive playground
Usage Patterns
Production patterns for SIWE authentication.Complete Authentication Flow
Frontend Implementation
Copy
Ask AI
// 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
Copy
Ask AI
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
Copy
Ask AI
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
Copy
Ask AI
// 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
Copy
Ask AI
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
Copy
Ask AI
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
Copy
Ask AI
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
Copy
Ask AI
// 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
Copy
Ask AI
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

