Skip to main content

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:

We take security seriously and will respond promptly.