Sign in with Ethereum (SIWE)
Sign in with Ethereum (SIWE)
Lipafy accepts EVM wallet signatures as a sign-in mechanism in addition to Google OAuth. The same wallet can later be used to sign approvals and intent mandates, so wallet sign-in is the cleanest path for crypto-native users who want one identity across all three.
The implementation follows EIP-4361 (Sign-In with Ethereum): server issues a nonce, client signs a structured message including the nonce, server verifies the signature against the claimed address.
Why a signature, not a password
- No password to leak — the wallet’s private key never leaves the user’s device.
- One identity for sign-in + approvals + mandates — the same key that authorizes a 5000 KES purchase via wallet signature is the one that signed you in.
- Multi-device by default — the wallet on your phone can authorize what the wallet on your laptop kicked off, because they’re both the same key.
The flow
Get a nonce
Returns:
The nonce is a server-side random value with a 10-minute TTL. Single-use — once verified, it’s burned.
Build the SIWE message
Standard EIP-4361 format:
Wallets render this as a human-readable prompt rather than opaque hex — that’s the whole point of EIP-4361.
Have the wallet sign it
Using viem:
MetaMask, Rabby, Brave Wallet, and most EIP-1193 providers handle this transparently. For multi-wallet pages (multiple injected providers), use window.ethereum.providers if available — see Multi-provider handling below.
Submit the signature
Lipafy:
- Parses the SIWE message and extracts
address,nonce,chain_id,expires_at. - Looks up the nonce; checks it hasn’t been used and hasn’t expired.
- Verifies the signature recovers to the claimed address (ECDSA, secp256k1).
- Marks the nonce as used.
- Looks up the account by linked wallet address. If none, creates a new account with
account_type='developer'. - Issues a session cookie (
Set-Cookie: lipafy_session=...) and returns a JWT for non-browser clients.
Response:
What binds a wallet to an account
The account_wallets table — one address can be linked to one account. The first time someone signs in with a wallet, Lipafy:
- Creates an account.
- Links the wallet address.
- Adds the address as a signing key for that account (so subsequent approvals signed by it are valid).
Adding more wallets to an existing account is available from the dashboard’s Account page. Sign in first, then use the wallet-linking action so approvals and mandates can be signed by that wallet.
Multi-provider handling
Modern browsers often have multiple injected providers (MetaMask + Rabby + Brave Wallet, etc.). window.ethereum is whichever loaded last, which may not be the one the user wants. Detect and let the user pick:
EIP-6963 (the newer “discover wallets via window events” pattern) is also worth supporting if you’re shipping a wallet-heavy frontend.
User rejection
If the user dismisses the wallet prompt:
- MetaMask throws with
code: 4001. - Other wallets typically use the same code (it’s the standard EIP-1193 user-rejected error).
- viem surfaces this as
UserRejectedRequestError.
Catch and surface a clean “you cancelled” rather than a stack trace:
Chain ID
The chain_id in the SIWE message is a binding — a signature valid on chain 1 is not valid on chain 137 (because the message includes the chain id literal). Lipafy currently records the chain id but doesn’t enforce a specific one for sign-in. For approvals and mandates, the chain id is similarly recorded; we may add per-account-allowed-chains in future for replay protection across chains.
Errors
Status code is 401 for all three.
Session cookie
Browser sign-in sets lipafy_session=<token>; HttpOnly; SameSite=Lax; Secure; Path=/. The token is hashed and stored in sessions; on every request, Lipafy looks it up by hash, so revoking a session is a single row delete with no token forgery risk even if the DB leaks.
Sign out: POST /v1/auth/logout (this session) or POST /v1/auth/logout/all (every session).
Comparison with Google OAuth
Most users will use Google. SIWE is the right choice when you want one identity across sign-in and on-chain-style authorizations.
Related
- Authentication overview
- Connected apps via OAuth
- Approvals — wallet signatures double as approval signatures
- Auth API reference
