Web3 Authentication with SIWE (Sign-In with Ethereum)

Passwords are dead. Learn how to let users log in with their crypto wallets securely using the SIWE standard.

📅 November 26, 2025•⏱️ 22 min read•🏷️ Web3

Introduction

"Connect Wallet" buttons are everywhere, but simply connecting a wallet doesn't prove ownership for a backend session. Anyone can pretend to be `0x123...` in an HTTP request.

Sign-In with Ethereum (EIP-4361) standardizes how off-chain authentication works. It challenges the user to sign a specific message, proving they control the private key.

💡 Why This Matters: SIWE brings the security of public-key cryptography to standard Web2 user sessions.

How It Works

Client Implementation

Using `siwe` and `wagmi` in Next.js to generate and sign the message.

SignIn.tsx
import { SiweMessage } from 'siwe';
import { useAccount, useSignMessage } from 'wagmi';

export function SignInButton() {
  const { address, chainId } = useAccount();
  const { signMessageAsync } = useSignMessage();

  const signIn = async () => {
    // 1. Get nonce from backend
    const nonceRes = await fetch('/api/nonce');
    const nonce = await nonceRes.text();

    // 2. Create message
    const message = new SiweMessage({
      domain: window.location.host,
      address,
      statement: 'Sign in with Ethereum to the app.',
      uri: window.location.origin,
      version: '1',
      chainId,
      nonce,
    });

    // 3. Sign message
    const signature = await signMessageAsync({
      message: message.prepareMessage(),
    });

    // 4. Verify on backend
    await fetch('/api/verify', {
      method: 'POST',
      body: JSON.stringify({ message, signature }),
    });
  };

  return <button onClick={signIn}>Sign In with Wallet</button>;
}

Backend Verification (Spring Boot)

The backend must verify that the signature matches the message and the address. It also checks the nonce to ensure freshness.

SiweController.java
@PostMapping("/verify")
public ResponseEntity<?> verify(@RequestBody SiweRequest request) {
    try {
        // Use a library like 'siwe-java' or verify signature manually using Web3j
        boolean isValid = siweService.verify(
            request.getMessage(),
            request.getSignature()
        );

        if (isValid) {
            String address = extractAddress(request.getMessage());
            // Create JWT Session for this address
            String token = jwtService.createToken(address);
            return ResponseEntity.ok(new AuthResponse(token));
        }
        return ResponseEntity.status(401).build();
    } catch (Exception e) {
        return ResponseEntity.status(401).body("Invalid signature");
    }
}

Security Analysis

SIWE is secure, but implementation details matter.

🔒 Nonce Validation

The nonce must be generated server-side and checked to prevent replay attacks.

🔒 Domain Checking

The message includes the domain to prevent phishing (signing a message on evil.com for good.com).

🔒 Expiration

The message should have an expiration time (`expirationTime` field).

🔒 HTTPS

Always serve your app over HTTPS to prevent MITM attacks on the signature transmission.

✅ SIWE Checklist

Before you ship:

Build Web3 Apps

Need to test your smart contract interactions? We have tools for that.

Related Topics

Conclusion

SIWE is a game-changer for user onboarding. It eliminates the need for passwords, reduces friction, and leverages the security of the blockchain.

By implementing SIWE correctly with backend verification, you open your application to the world of Web3 while maintaining robust security standards.

🌌
Purple Dream
Active Theme