This document describes the implementation of dynamic nonce-based authentication for Sign-In with Ethereum (SIWE) to prevent replay attacks.
Sign-In with Ethereum (SIWE) is an authentication standard that allows users to authenticate with applications using their Ethereum wallet. Users sign a standardized message with their private key, proving ownership of their wallet address.
Without nonce protection, a malicious actor could:
- Capture a user's signed SIWE message
- Reuse that signature to authenticate as the user
- Perform unauthorized actions on behalf of the user
Each authentication request includes a unique nonce that:
- Starts at
1for new users - Increments after each successful authentication
- Is unique per wallet address
- Prevents signature reuse
-- Migration: 003_add_nonces.sql
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS login_nonce BIGINT NOT NULL DEFAULT 1;GET /auth/nonce/:wallet_address
Returns the current nonce for a wallet address:
{
"nonce": 1,
"address": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
}- Frontend requests nonce from
/auth/nonce/:address - Backend returns current nonce (starts at 1 for new users)
- Frontend generates SIWE message:
Sign this message to authenticate with The Guild. Nonce: 1 - User signs message with MetaMask
- Frontend sends signature to protected endpoint
- Backend verifies signature against reconstructed message
- Backend increments nonce for next authentication
- Replay Attack Prevention: Each nonce can only be used once
- User Isolation: Each wallet has independent nonce sequence
- Fresh Authentication: No cached nonces - always current
- Cryptographic Security: Uses ethers.js for proper signature verification
src/domain/repositories/profile_repository.rs- Repository interfacesrc/infrastructure/repositories/postgres_profile_repository.rs- PostgreSQL implementationsrc/application/queries/get_login_nonce.rs- Application querysrc/presentation/handlers.rs- API handlersrc/infrastructure/services/ethereum_address_verification_service.rs- Signature verification
src/hooks/profiles/use-get-nonce.ts- Nonce fetching hooksrc/lib/utils/siwe.ts- SIWE message generationsrc/components/profiles/action-buttons/- Updated UI components
// 1. Fetch nonce
const { data: nonceData } = useGetNonce(walletAddress);
// 2. Generate SIWE message
const message = generateSiweMessage(nonceData.nonce);
// 3. Sign with wallet
const signature = await signMessageAsync({ message });
// 4. Create profile
await createProfile.mutateAsync({
input: { name: "John Doe", description: "Developer" },
signature
});// Reconstruct expected message
let expected_message = format!(
"Sign this message to authenticate with The Guild.\n\nNonce: {}",
nonce
);
// Verify signature
let recovered_address = signature.recover(expected_message)?;
// Increment nonce after successful verification
profile_repository.increment_login_nonce(&wallet_address).await?;- Start backend:
cargo run --bin guild-backend - Start frontend:
npm run dev - Connect MetaMask wallet
- Create profile → nonce
1 - Update profile → nonce
2 - Delete profile → nonce
3
# Backend tests
cargo test
# Frontend tests
npm test- Database migration applied
- SQLX cache updated (
cargo sqlx prepare) - Environment variables configured
- CORS settings updated for frontend domain
- Rate limiting configured (recommended)
- Rate Limiting: Implement rate limiting on nonce/auth endpoints
- Monitoring: Log authentication failures and nonce patterns
- Database Constraints: Consider adding nonce validation constraints
- Audit: Regular security audits of authentication flow
- Nonce Expiration: Add time-based nonce expiration
- Concurrent Request Handling: Database-level atomic nonce operations
- Advanced Monitoring: Authentication metrics and alerts
- Multi-device Support: Handle multiple simultaneous sessions
This implementation provides robust protection against replay attacks while maintaining a smooth user experience. The dynamic nonce system ensures that each authentication request is unique and cannot be reused, significantly improving the security of the SIWE authentication flow.
For questions or contributions, please refer to the implementation code or create an issue in the repository.