Security Best Practices
Guidelines for securely integrating the Web3 Identity API into your applications.
Authentication Securityโ
Protect Your API Keysโ
// โ WRONG - Never hardcode keys
const API_KEY = 'sk_live_abc123...';
// โ
CORRECT - Use environment variables
const API_KEY = process.env.API_KEY;
Best practices:
- Store API keys in environment variables
- Never commit keys to version control
- Use different keys for development and production
- Rotate keys periodically
- Revoke compromised keys immediately
SIWE Session Managementโ
// โ
Secure SIWE implementation
const siweConfig = {
// Validate the domain matches your app
domain: window.location.host,
// Use a secure, unique nonce
nonce: await fetchNonceFromServer(),
// Set reasonable expiration
expirationTime: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
// Bind to specific chain
chainId: 1,
};
Session security:
- Verify SIWE signatures server-side
- Use short session expiration times (24h max)
- Implement session revocation
- Store session tokens securely (httpOnly cookies)
Request Securityโ
Validate All Inputsโ
// โ
Validate before sending to API
function validateENSName(name) {
// Must end in .eth and be reasonable length
if (!name.endsWith('.eth')) return false;
if (name.length < 7 || name.length > 100) return false;
// No dangerous characters
if (/[<>"'&]/.test(name)) return false;
return true;
}
function validateAddress(address) {
return /^0x[a-fA-F0-9]{40}$/.test(address);
}
// Use validation
if (!validateENSName(userInput)) {
throw new Error('Invalid ENS name');
}
const data = await fetch(`${API_BASE}/api/ens/${userInput}`);
Use HTTPS Onlyโ
// โ
Always use HTTPS
const API_BASE = 'https://api.web3identity.com';
// โ Never use HTTP
const API_BASE = 'http://api.web3identity.com'; // INSECURE
Implement Request Timeoutsโ
// โ
Set reasonable timeouts
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
try {
const res = await fetch(url, { signal: controller.signal });
clearTimeout(timeout);
return res.json();
} catch (e) {
if (e.name === 'AbortError') {
throw new Error('Request timed out');
}
throw e;
}
Data Handlingโ
Sanitize API Responsesโ
// โ
Sanitize before displaying user-generated content
import DOMPurify from 'dompurify';
async function displayENSProfile(name) {
const profile = await fetch(`${API_BASE}/api/ens/${name}`).then(r => r.json());
// Sanitize text records before rendering
const description = DOMPurify.sanitize(profile.records?.description || '');
const url = sanitizeURL(profile.records?.url);
return { ...profile, description, url };
}
function sanitizeURL(url) {
if (!url) return null;
try {
const parsed = new URL(url);
// Only allow http/https
if (!['http:', 'https:'].includes(parsed.protocol)) return null;
return parsed.href;
} catch {
return null;
}
}
Don't Trust User-Controlled Dataโ
ENS text records, Farcaster bios, and other user-generated content can contain:
- Malicious scripts (XSS)
- Phishing URLs
- Misleading information
// โ
Validate URLs from ENS records before redirecting
const websiteRecord = profile.records?.url;
if (websiteRecord) {
const url = new URL(websiteRecord);
// Warn users about external links
if (!trustedDomains.includes(url.hostname)) {
showWarning(`You're leaving to: ${url.hostname}`);
}
}
Wallet Securityโ
Private Key Protectionโ
// โ NEVER log or expose private keys
console.log(wallet.privateKey); // DANGEROUS
// โ NEVER send private keys to any API
fetch('/api/something', {
body: JSON.stringify({ privateKey: wallet.privateKey }) // DANGEROUS
});
// โ
Only sign messages, never expose keys
const signature = await wallet.signMessage(message);
Verify Contract Interactionsโ
When using wallet data from the API:
// โ
Verify addresses before sending transactions
async function verifyBeforeSend(ensName, amount) {
// Resolve ENS to address
const { address } = await fetch(`${API_BASE}/api/ens/resolve/${ensName}`)
.then(r => r.json());
// Double-check with user
const confirmed = await showConfirmation(
`Send ${amount} ETH to ${ensName} (${address})?`
);
if (!confirmed) return;
// Verify address format
if (!ethers.isAddress(address)) {
throw new Error('Invalid address returned');
}
// Proceed with transaction
return sendTransaction(address, amount);
}
x402 Payment Securityโ
Verify Payment Requirementsโ
// โ
Verify 402 response before paying
async function handlePaymentRequired(response) {
if (response.status !== 402) return null;
const details = await response.json();
// Verify payment details
if (!details.paymentDetails) {
throw new Error('Invalid payment response');
}
const { maxAmountRequired, resource, payTo, network } = details.paymentDetails;
// Validate network is Base
if (network !== 'base') {
throw new Error('Unexpected network');
}
// Validate amount is reasonable
const maxUSD = Number(maxAmountRequired) / 1e6; // USDC has 6 decimals
if (maxUSD > 1.00) {
throw new Error(`Unusually high price: $${maxUSD}`);
}
// Show user what they're paying for
const confirmed = await showPaymentConfirmation({
endpoint: resource,
amount: maxUSD,
recipient: payTo,
});
if (!confirmed) return null;
return signPayment(details);
}
Set Spending Limitsโ
// โ
Implement spending limits
class SpendingTracker {
constructor(dailyLimit = 10.00) {
this.dailyLimit = dailyLimit;
this.spent = 0;
this.resetTime = this.getNextMidnight();
}
canSpend(amount) {
this.checkReset();
return (this.spent + amount) <= this.dailyLimit;
}
recordSpend(amount) {
if (!this.canSpend(amount)) {
throw new Error(`Daily limit ($${this.dailyLimit}) exceeded`);
}
this.spent += amount;
}
checkReset() {
if (Date.now() > this.resetTime) {
this.spent = 0;
this.resetTime = this.getNextMidnight();
}
}
getNextMidnight() {
const tomorrow = new Date();
tomorrow.setUTCHours(24, 0, 0, 0);
return tomorrow.getTime();
}
}
const spending = new SpendingTracker(10.00); // $10/day max
Rate Limiting & Abuse Preventionโ
Implement Client-Side Rate Limitingโ
// โ
Rate limit your own requests
class RateLimiter {
constructor(maxPerMinute = 30) {
this.maxPerMinute = maxPerMinute;
this.requests = [];
}
async acquire() {
const now = Date.now();
this.requests = this.requests.filter(t => now - t < 60000);
if (this.requests.length >= this.maxPerMinute) {
const waitTime = 60000 - (now - this.requests[0]);
await new Promise(r => setTimeout(r, waitTime));
return this.acquire();
}
this.requests.push(now);
}
}
const limiter = new RateLimiter(30);
async function rateLimitedFetch(url) {
await limiter.acquire();
return fetch(url);
}
Cache Aggressivelyโ
// โ
Cache responses to reduce API calls
const cache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
async function cachedFetch(url) {
const cached = cache.get(url);
if (cached && Date.now() - cached.time < CACHE_TTL) {
return cached.data;
}
const data = await fetch(url).then(r => r.json());
cache.set(url, { data, time: Date.now() });
return data;
}
Error Handlingโ
Don't Expose Internal Errorsโ
// โ Don't expose raw errors to users
catch (error) {
res.status(500).json({ error: error.stack }); // Exposes internals
}
// โ
Return safe error messages
catch (error) {
console.error('API Error:', error); // Log internally
res.status(500).json({
error: 'An error occurred',
code: 'INTERNAL_ERROR'
});
}
Log Security Eventsโ
// โ
Log security-relevant events
function logSecurityEvent(event, details) {
console.log(JSON.stringify({
type: 'SECURITY',
event,
...details,
timestamp: new Date().toISOString(),
ip: getClientIP(),
}));
}
// Usage
logSecurityEvent('RATE_LIMIT_EXCEEDED', { userId, endpoint });
logSecurityEvent('INVALID_SIGNATURE', { address, message });
logSecurityEvent('PAYMENT_FAILED', { amount, reason });
Checklistโ
Before Going to Productionโ
- API keys stored in environment variables
- HTTPS enforced everywhere
- Input validation on all user inputs
- Output sanitization for displayed data
- Rate limiting implemented
- Spending limits set for x402 payments
- Error messages don't expose internals
- Security logging enabled
- SIWE sessions expire appropriately
- Caching reduces unnecessary API calls
Ongoing Securityโ
- Rotate API keys periodically
- Monitor for unusual usage patterns
- Keep dependencies updated
- Review security logs regularly
- Test with malicious inputs periodically
Reporting Security Issuesโ
Found a vulnerability? Contact us:
- Email: support@web3identity.com
- Subject: Security Issue - [Brief Description]
We take security seriously and will respond promptly.