Building secure, wallet-authenticated APIs.
- Security Model Overview
- Production Checklist
- Attack Vectors & Mitigations
- Configuration Best Practices
- Reporting Security Issues
WebX403's security relies on four pillars:
Every request is signed with the user's Ed25519 private key. The server verifies the signature against the wallet's public key. Without the private key, authentication is impossible.
Challenges expire after 60 seconds (configurable). Old challenges cannot be reused, limiting the window for attacks.
Each authentication uses a unique nonce. The server tracks used nonces to prevent replay attacks.
Signatures can be bound to specific HTTP methods and paths, preventing signature reuse across different endpoints.
Before deploying WebX403 to production, ensure you've implemented these safeguards:
- Use HTTPS exclusively – Never run WebX403 over plain HTTP in production
- Enable replay protection – Use Redis-backed nonce store (not in-memory)
- Set short TTLs – Keep challenge TTL ≤ 60 seconds
- Bind method + path – Enable
bindMethodPath: true - Monitor for anomalies – Log failed auth attempts
- Enable origin binding – Prevent CSRF-style attacks
- Rate limit challenge endpoints – Prevent DoS via challenge generation
- Implement token gating – Restrict access by NFT/token ownership
- Use a CDN with DDoS protection – Cloudflare, AWS CloudFront, etc.
- Rotate server identifiers – Change
issuerperiodically
- User-Agent binding – Lock challenges to specific browsers
- IP allowlisting – For internal APIs or admin panels
- Multi-signature schemes – Require multiple wallet approvals
- Audit logging – Store all authentication events
Attack: Attacker captures a valid Authorization header and replays it.
Mitigation:
- Each nonce is single-use and tracked in a replay store
- Challenges have short TTLs (60s)
- Configure Redis for distributed replay tracking:
import { createWebX403 } from 'webx403-server';
import Redis from 'ioredis';
const redis = new Redis();
const auth = createWebX403({
issuer: 'my-api',
audience: 'https://api.example.com',
replayStore: {
async check(key, ttl) {
return await redis.exists(key) === 1;
},
async store(key, ttl) {
await redis.setex(key, ttl, '1');
}
}
});Attack: Attacker intercepts challenge/response over unencrypted connection.
Mitigation:
- Mandatory HTTPS – Encrypt all traffic
- HSTS headers – Force browsers to use HTTPS
- Certificate pinning – For high-security mobile apps
// Express example: Force HTTPS
app.use((req, res, next) => {
if (!req.secure && process.env.NODE_ENV === 'production') {
return res.redirect(`https://${req.headers.host}${req.url}`);
}
next();
});Attack: Attacker requests many challenges to analyze patterns or find weaknesses.
Mitigation:
- Rate limiting – Limit challenges per IP/user
- Challenge quotas – Max N challenges per wallet per hour
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use(limiter);Attack: Malicious site tricks user into making authenticated requests.
Mitigation:
- Origin binding – Validate
Originheader matches expected domain - SameSite cookies – If using cookies alongside WebX403
const auth = createWebX403({
issuer: 'my-api',
audience: 'https://api.example.com',
originBinding: true // ✅ Enable this
});Attack: Attacker modifies signature to create valid alternative signature.
Mitigation:
- Ed25519 signatures are not malleable by design
- WebX403 uses Ed25519 exclusively (no ECDSA)
- No additional mitigation needed
| Setting | Development | Production |
|---|---|---|
| HTTPS | Optional | Mandatory |
| Replay Store | In-Memory | Redis/DynamoDB |
| TTL | 300s (5 min) | 60s (1 min) |
| Origin Binding | Off | On |
| Method/Path Binding | Optional | On |
| Rate Limiting | Off | On (100 req/15min) |
import { createWebX403 } from 'webx403-server';
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
const auth = createWebX403({
issuer: 'myapp-api-v1',
audience: 'https://api.myapp.com',
ttlSeconds: 60,
bindMethodPath: true,
originBinding: true,
clockSkewSeconds: 30, // Tolerate ±30s clock drift
replayStore: {
async check(key, ttl) {
return await redis.exists(key) === 1;
},
async store(key, ttl) {
await redis.setex(key, ttl, '1');
}
}
});When implementing token gates, cache results to prevent RPC spam:
import NodeCache from 'node-cache';
const cache = new NodeCache({ stdTTL: 600 }); // 10-minute cache
const auth = createWebX403({
issuer: 'nft-club',
audience: 'https://api.club.io',
tokenGate: async (address) => {
// Check cache first
const cached = cache.get(address);
if (cached !== undefined) return cached;
// Query blockchain
const hasNFT = await checkNFTOwnership(address, 'COLLECTION_MINT');
// Cache result
cache.set(address, hasNFT);
return hasNFT;
}
});WebX403 tolerates clock differences between client and server:
- Default tolerance: ±120 seconds
- Configurable:
clockSkewSecondsoption - Recommendation: Keep servers NTP-synced
const auth = createWebX403({
// ... other options
clockSkewSeconds: 30 // ±30s tolerance (stricter)
});- Authentication success rate – Should be >95%
- Challenge generation rate – Sudden spikes indicate potential abuse
- Replay detection rate – Non-zero means attacks are being blocked
- Failed signature verifications – High rate = client bugs or attacks
app.use((req, res, next) => {
const originalSend = res.json;
res.json = function(data) {
if (res.statusCode === 403 && data.error) {
console.warn('Auth failure:', {
ip: req.ip,
path: req.path,
error: data.error,
timestamp: new Date().toISOString()
});
}
return originalSend.call(this, data);
};
next();
});DO NOT open public GitHub issues for security vulnerabilities.
Instead, email: security@byrgerbib.dev
Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
We aim to respond within 48 hours and resolve critical issues within 7 days.
Planned future enhancements:
- Multi-sig support – Require N-of-M wallet approvals
- Hardware wallet integration – Ledger, Trezor support
- Audit logging backend – PostgreSQL/MongoDB integration
- Anomaly detection ML – Auto-block suspicious patterns
Security is a shared responsibility. Follow these guidelines, stay updated, and report issues promptly.
Last updated: November 2025