“Connect wallet” tells you a provider exists. It doesn’t prove identity to your backend, and it doesn’t protect you from replay.
Users can switch accounts, refresh tabs, or sign once and then come back later with a confusing state that looks like a login but isn’t one.
Sign-In with Ethereum (SIWE) fills that gap by standardizing a message a user signs and a verification process that your server can validate. It’s still your job to create a session, handle web security realities, and keep identity stable when wallets change. When those pieces are consistent, SIWE feels like a normal login, just powered by a signature instead of a password.
What SIWE is used for
SIWE is defined by EIP-4361 as a standard way for Ethereum accounts to authenticate with off-chain services by signing a message that includes scope and security mechanisms such as a nonce. The signature proves control of a specific address at a specific moment, which is exactly what a backend needs to establish an authenticated session without relying on email or passwords.
SIWE is not a permission system, and it isn’t on-chain authorization. It won’t tell you whether a user owns an NFT, has tokens, or belongs to a DAO. Think of it as the identity proof step that unlocks your usual web auth machinery: sessions, rate limits, and server-side checks.
The SIWE fields that actually protect you
EIP-4361 messages include many fields, but in production, a few of them do the heavy lifting. These are the ones that stop a signed message from being “a reusable login token that works everywhere.”
Domain and URI bind the signature to your app
The domain and URI fields are the primary “this message is for this site” binding. If you treat them as decorative, a signature can become portable: a user signs in one context, and that same signed message might be accepted somewhere else if your backend verification is sloppy. EIP-4361 includes these fields so the message is human-readable and so servers have enough information to enforce relying-party checks.
The practical rule is to validate them server-side against your expected values. Letting the frontend decide the domain is convenient and risky. A stricter approach is to have the backend provide the canonical domain (and often the canonical URI), then verify that the signed message matches that policy exactly during verification.
Nonce prevents replay
The SIWE nonce is an authentication nonce, not an Ethereum account nonce. It exists to prevent replay attacks by making each login attempt unique. If a nonce can be reused, then a signature can be reused, which effectively turns “sign once” into “log in forever.”
In a production-safe design, the server issues the nonce, stores it, and treats it as single-use. Reown’s SIWE reference flow explicitly includes a nonce route and verification that relies on a nonce stored in the session. This also makes concurrency survivable: if a user has two tabs open, you can invalidate older nonces cleanly and guide the UI to request a fresh nonce rather than retrying a doomed verification.
Time fields reduce ambiguity and limit stale proofs
EIP-4361 supports time attributes such as issuedAt, notBefore, and expirationTime. These aren’t a replacement for nonce checks, but they add a second layer of safety and make debugging a lot easier. Without time bounds, a user can dig up an old signed message and keep trying it until some edge case accepts it.
A common pattern is to keep SIWE messages valid for a short window while keeping the server session valid longer. That way, the signature is a short-lived proof step, and your app’s actual login state is handled by your session layer, where you can revoke it, rotate it, and protect it with normal web controls.
A SIWE login flow that stays predictable
A SIWE flow works best when it looks like a normal login flow: a short proof step followed by a durable session. Most implementations converge on the same endpoints because the backend needs to control replay protection and session state.
Nonce endpoint that stays server-controlled
The backend should issue a fresh nonce and store it in a server-side session or a short-lived store keyed to the client. Reown’s reference SIWE integration shows a GET/nonce route whose purpose is “single-use random number,” and a verification route that expects the nonce stored in the session. This makes it much harder for attackers to replay signatures and much easier for you to reason about failures.
Treat nonce issuance as a first-class API contract. If verification fails because of a nonce mismatch, the frontend shouldn’t keep retrying. It should fetch a new nonce and build a new SIWE message. That one small UX choice eliminates a lot of “sometimes it works” bug reports.
Signing step that uses the wallet’s normal message signing
The client constructs the SIWE message using the server-issued nonce and the relying-party details you expect, then asks the wallet to sign it. Some wallets even validate SIWE-style messages at signing time when they recognize the standard format, which improves user clarity during approval.
The important product point is that signing should happen only after the nonce is received. “Sign first, then ask the server” is backwards and makes replay control harder. If you keep the sequence clean, your frontend can treat SIWE signing like a standard login prompt, and your backend can treat it like a standard credential check.
Verification endpoint that enforces more than cryptography
The backend receives the message and signature and verifies them. Verification is not only “does the signature match the address.” It should also enforce the security fields you care about, especially domain binding and nonce matching. SpruceID’s SIWE implementations explicitly model verification failures such as domain mismatch and nonce mismatch, which is a good signal of what production-grade verification is expected to check.
After successful verification, the backend should mark the nonce as consumed and establish an authenticated session. At that moment, the signature has done its job. You should avoid treating the signature itself as a bearer token, because that encourages reuse and expands the blast radius of any leakage.
Session handling after SIWE
SIWE proves identity. Your session design controls everyday security. This is where web app trade-offs still apply, because SIWE doesn’t eliminate CSRF, XSS, or session fixation risks by itself.
For a browser app, server-side sessions with HTTP-only cookies are often the simplest approach because the session token stays out of JavaScript and is easy to revoke. If you use cookies, CSRF needs an explicit stance, often via SameSite settings plus CSRF tokens when required. If you use a bearer token approach in JavaScript-accessible storage, CSRF risk drops but XSS impact rises. SIWE works with either, but the choice should be deliberate because it shapes the real security profile more than the signature step does.
What to do when the wallet account changes after sign-in
Account switching is normal. If your app doesn’t treat it as a session boundary, you can end up with “authenticated as address A, acting as address B,” which is confusing and sometimes dangerous. EIP-1193 requires providers to emit accountsChanged and chainChanged events when those states change.
A stable approach is to bind the server session to one address and reflect that address in your UI. When the connected wallet account changes, compare it to the authenticated address. If they differ, you either prompt the user to sign in again with the new address or you keep them signed in but clearly indicate the mismatch. What you want to avoid is silently switching identity, because that’s how users end up approving actions with an account they didn’t intend.
Common SIWE integration pitfalls that cause real production bugs
Nonce reuse that turns a signature into a reusable login token
Nonce reuse is the fastest way to break SIWE security. It happens when nonces are generated on the client, when they aren’t stored server-side, or when the server verifies a message and forgets to mark the nonce as consumed. It also shows up as a reliability issue in multi-tab scenarios where one verification consumes the nonce and the other tab retries with stale data, creating intermittent “invalid nonce” errors that look random to users.
The fix is strict and boring: server-issued nonce, stored server-side, short-lived, single-use, consumed on success. If verification fails because of nonce mismatch, treat it as “fetch a new nonce and try again,” not “retry the same payload.” That turns SIWE from flaky to predictable.
Domain and URI values that make a signature valid in the wrong place
A cryptographically valid signature can still be invalid for your service. If your verification accepts an arbitrary domain or doesn’t check the URI, you can accept signed messages that were not intended for your app, especially across environments like staging and preview deployments. SpruceID’s SIWE libraries even define domain mismatch and nonce mismatch as first-class verification errors, which is a strong hint that these checks are expected in serious implementations.
Treat domain and URI as backend policy. Validate them strictly against what you operate. If you have multiple domains, codify the allowlist rules and keep them versioned, not improvised in frontend code. This one control prevents a large class of cross-site signature reuse.
Account switching that creates “signed in as A, acting as B”
This bug happens when the frontend treats “connected account” as “authenticated account” without verifying it against the server session. Wallets can change accounts between requests, and users do it more often than most teams expect. EIP-1193’s accountsChanged event exists precisely because this is normal behavior.
The safest posture is to keep the authenticated address in the session state and compare it with the currently connected address on sensitive actions. If they differ, require a new SIWE proof to switch identity. If you decide to allow a mismatch temporarily, make it visible and explicit. Silent mismatch is what creates the “I didn’t mean to do that” moments.
Treating SIWE as authorization instead of authentication
SIWE proves control of a key, nothing more. It doesn’t confirm holdings, roles, or permissions. When teams treat SIWE as authorization, they accidentally grant access based on a signature alone, which is usually not the business rule they intended. EIP-4361 is about authentication with scope and session details, not about on-chain entitlement checks.
Keep authorization separate. If access depends on token ownership or DAO membership, check that explicitly against chain state through your own trusted data source after SIWE authentication succeeds. That keeps your logic honest and avoids building a brittle “signature equals permission” system.
Missing time limits that keep old signed messages usable
If you omit time constraints entirely, a signed message can remain usable longer than you think, especially if any part of your nonce lifecycle fails open in a corner case. Time fields such as expirationTime and notBefore exist to constrain when a signature should be accepted. This improves both security and operability, because “this proof expired” is easier to reason about than “sometimes invalid.”
A practical policy is a short SIWE message validity with longer server sessions. That gives you a tight window for the proof step while keeping normal “stay signed in” behavior intact. It also keeps support easier: if a user tries to authenticate with a stale signature, the error is clear and consistent.
Conclusion
SIWE is most effective when it stays a short proof step inside a normal authentication system. EIP-4361 gives you the standardized message fields, so servers can verify signatures predictably. The production work is enforcing those fields strictly, then running a session layer that behaves like a normal web app when users switch accounts, switch chains, refresh tabs, and come back later.
If your SIWE implementation is boring in the best way (server-issued nonces, strict domain checks, sensible time windows, and sessions tied to one address), then SIWE stops feeling like a crypto trick and starts behaving like authentication.




