Setup
Add the plugin
lib/kavach.ts
Sign-in flow
SIWE requires three steps: get a nonce, sign a message in the wallet, then submit both to the server.Get a nonce
GET /auth/siwe/nonceRequest a server-generated nonce before building the sign-in message. Nonces are single-use and expire after 5 minutes (configurable).Build and sign the message
Construct the EIP-4361 message string from the user’s address, the nonce, and your app metadata. Pass it to the wallet for signing.Verify and start a session
POST /auth/siwe/verifySubmit the original message and the wallet signature. On success, the server returns the verified Ethereum address and chain ID. Create a session with your session management layer from here.200 OKNonce lifecycle
Every sign-in attempt must use a fresh nonce from the server. Nonces:- Are 32 random hex bytes (256 bits of entropy)
- Expire after
nonceTtlSeconds(default: 300 seconds) - Are deleted immediately after a successful or failed verification, they cannot be reused
(message, signature) pair from one session cannot be submitted again.
If the nonce expires before the user signs, the verify endpoint returns 400 with "Nonce expired". Request a new nonce and rebuild the message.
Linking wallets to users
SIWE verifies an address, it does not create or look up a user by itself. After a successful/auth/siwe/verify, use the returned address to find an existing user record or create a new one:
Endpoints
| Endpoint | Method | Auth required | Description |
|---|---|---|---|
/auth/siwe/nonce | GET | No | Generate a single-use nonce |
/auth/siwe/verify | POST | No | Verify message and signature, return address |
Configuration reference
| Option | Type | Default | Description |
|---|---|---|---|
domain | string | required | Your app’s domain, shown in the wallet prompt (e.g. example.com) |
uri | string | required | Full origin URI (e.g. https://example.com). Must match what the wallet signed |
statement | string | , | Human-readable statement shown in the wallet (e.g. Sign in to Example App) |
nonceTtlSeconds | number | 300 | How long a nonce is valid before it expires |
verifySignature | (message, signature) => Promise<string> | , | Custom secp256k1 recovery function. Returns the recovered address. Required in production |
Security considerations
Always addverifySignature in production. Without it, the plugin validates message structure and nonce state, but anyone can submit a well-formed SIWE message for any address without a real signature.
Domain and URI binding. The plugin rejects messages where domain or uri do not match the server’s config. This prevents phishing attacks where a malicious site captures a signature meant for a different origin.
Nonce reuse prevention. Nonces are deleted on first use regardless of whether verification succeeds. A second attempt with the same nonce always fails.
Chain ID. The chain ID in the message is returned to your application but is not enforced by the plugin. If your app is chain-specific (e.g. only Ethereum mainnet), check that chainId === 1 (or your expected value) after verification.