What it does
- Frontend asks Pylon for a fresh nonce for the user’s address.
- Frontend builds an EIP-4361 message (
Sign in to acme.com\n\nAddress: 0x...\n...nonce: ...\n...) and asks the user’s wallet to sign it viapersonal_sign. - Pylon recovers the address from the signature using secp256k1 ECDSA + Keccak-256 (Ethereum’s variant), validates the message’s domain + nonce + expiry, and mints a session keyed on the wallet.
Endpoints
| Endpoint | Method | Auth | Purpose |
|---|---|---|---|
/api/auth/siwe/nonce | GET | none | Mint a single-use nonce for ?address=0x... |
/api/auth/siwe/verify | POST | none | Verify the signed message; mint session |
Schema
The user entity needs awalletAddress field:
unique so two accounts can’t claim the same address.
Sign-in flow
1. Request a nonce
| Status | Code | Reason |
|---|---|---|
| 400 | INVALID_ADDRESS | Not 0x + exactly 40 hex chars |
2. Build + sign the message
The frontend constructs an EIP-4361 message and asks the wallet to sign it (MetaMask, WalletConnect, Coinbase Wallet, etc.):3. Verify
displayName is optional — used only when Pylon creates a new User row for a first-time address. Default is the short-form 0x742d…beb0.
Errors:
| Status | Code | Reason |
|---|---|---|
| 400 | SIWE_BAD_MESSAGE | EIP-4361 message couldn’t be parsed |
| 401 | SIWE_VERIFY_FAILED | Signature didn’t recover to the message’s address, nonce unknown / consumed, domain mismatch, expired |
Verification details
Pylon validates:- Signature recovery — secp256k1 ECDSA + Keccak-256 hash of the EIP-191 prefix (
\x19Ethereum Signed Message:\n<len>+ message) recovers a 20-byte address. Must matchmessage.addresscase-insensitively. - Nonce — must exist in the nonce store, must be bound to
message.address, single-use (consumed on first verify). - Domain —
message.domainmust match the request’sHostheader. Prevents replay across deployments. - Issued / expiration — if
message.expirationTimeis set, must be in the future. Ifmessage.notBeforeis set, must be in the past.
0x742d35Cc... and lowercase 0x742d35cc... collapse to the same User row.
Security guarantees
- secp256k1 + Keccak-256 via the
k256crate — same primitives Ethereum uses, audited. - Nonce binding to address — using
nonce-for-Ato sign-in-as-B fails verify. - Single-use nonces — consumed on first verify regardless of success.
- Domain pinning —
message.domainvalidated againstHostheader; replay across deployments rejected. - Expiration enforcement —
expirationTimeandnotBeforehonored. - EIP-191 prefix properly applied — defeats signature reuse from any other Ethereum-signed payload.