使用以太坊登录 (SIWE)

以太坊登录(SIWE)插件允许用户使用他们的以太坊钱包进行身份验证,遵循 ERC-4361 标准。该插件提供了灵活性,允许你实现自己的消息验证和随机数生成逻辑。

【The Sign in with Ethereum (SIWE) plugin allows users to authenticate using their Ethereum wallets following the ERC-4361 standard. This plugin provides flexibility by allowing you to implement your own message verification and nonce generation logic.】

安装

【Installation】

添加服务器插件

将 SIWE 插件添加到你的认证配置中:

auth.ts
import { betterAuth } from "better-auth";
import { siwe } from "better-auth/plugins";

export const auth = betterAuth({
    plugins: [
        siwe({
            domain: "example.com",
            emailDomainName: "example.com", // optional
            anonymous: false, // optional, default is true
            getNonce: async () => {
                // Implement your nonce generation logic here
                return "your-secure-random-nonce";
            },
            verifyMessage: async (args) => {
                // Implement your SIWE message verification logic here
                // This should verify the signature against the message
                return true; // return true if signature is valid
            },
            ensLookup: async (args) => {
                // Optional: Implement ENS lookup for user names and avatars
                return {
                    name: "user.eth",
                    avatar: "https://example.com/avatar.png"
                };
            },
        }),
    ],
});

迁移数据库

运行迁移或生成架构,以向数据库添加必要的字段和表。

npx @better-auth/cli migrate
npx @better-auth/cli generate

See the Schema section to add the fields manually.

添加客户端插件

auth-client.ts
import { createAuthClient } from "better-auth/client";
import { siweClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
    plugins: [siweClient()],
});

用法

【Usage】

生成一次性随机数

【Generate a Nonce】

在签署 SIWE 消息之前,你需要为钱包地址生成一个随机数(nonce):

【Before signing a SIWE message, you need to generate a nonce for the wallet address:】

generate-nonce.ts
const { data, error } = await authClient.siwe.nonce({
  walletAddress: "0x1234567890abcdef1234567890abcdef12345678",
  chainId: 1, // optional for Ethereum mainnet, required for other chains. Defaults to 1
});

if (data) {
  console.log("Nonce:", data.nonce);
}

使用以太坊登录

【Sign In with Ethereum】

生成随机数并创建 SIWE 消息后,验证签名以进行身份验证:

【After generating a nonce and creating a SIWE message, verify the signature to authenticate:】

sign-in-siwe.ts
const { data, error } = await authClient.siwe.verify({
  message: "Your SIWE message string",
  signature: "0x...", // The signature from the user's wallet
  walletAddress: "0x1234567890abcdef1234567890abcdef12345678",
  chainId: 1, // optional for Ethereum mainnet, required for other chains. Must match Chain ID in SIWE message
  email: "user@example.com", // optional, required if anonymous is false
});

if (data) {
  console.log("Authentication successful:", data.user);
}

链特定示例

【Chain-Specific Examples】

以下是针对不同区块链网络的示例:

【Here are examples for different blockchain networks:】

ethereum-mainnet.ts
// Ethereum Mainnet (chainId can be omitted, defaults to 1)
const { data, error } = await authClient.siwe.verify({
  message,
  signature,
  walletAddress,
  // chainId: 1 (default)
});
polygon.ts
// Polygon (chainId REQUIRED)
const { data, error } = await authClient.siwe.verify({
  message,
  signature,
  walletAddress,
  chainId: 137, // Required for Polygon
});
arbitrum.ts
// Arbitrum (chainId REQUIRED)
const { data, error } = await authClient.siwe.verify({
  message,
  signature,
  walletAddress,
  chainId: 42161, // Required for Arbitrum
});
base.ts
// Base (chainId REQUIRED)
const { data, error } = await authClient.siwe.verify({
  message,
  signature,
  walletAddress,
  chainId: 8453, // Required for Base
});

chainId 必须与你的 SIWE 消息中指定的链 ID 相匹配。如果消息的链 ID 与 chainId 参数不一致,验证将失败并返回 401 错误。

配置选项

【Configuration Options】

服务器选项

【Server Options】

SIWE 插件接受以下配置选项:

【The SIWE plugin accepts the following configuration options:】

  • 域名:你的应用的域名(生成 SIWE 消息时必填)
  • emailDomainName:在不使用匿名模式时创建用户账户的电子邮件域名。默认使用你的基础 URL 的域名
  • 匿名:是否允许匿名登录而无需提供电子邮件。默认值为 true
  • getNonce:用于为每次登录尝试生成唯一随机数的函数。你必须实现此函数以返回一个加密安全的随机字符串。必须返回一个 Promise<string>
  • verifyMessage:用于验证已签名 SIWE 消息的函数。接收消息详情,并应返回 Promise<boolean>
  • ensLookup:可选函数,用于查找以太坊地址的 ENS 名称和头像

客户选项

【Client Options】

SIWE 客户端插件不需要任何配置选项,但如果将来需要扩展,你可以传递它们:

【The SIWE client plugin doesn't require any configuration options, but you can pass them if needed for future extensibility:】

auth-client.ts
import { createAuthClient } from "better-auth/client";
import { siweClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
  plugins: [
    siweClient({
      // Optional client configuration can go here
    }),
  ],
});

架构

【Schema】

SIWE 插件添加了一个 walletAddress 表用于存储用户钱包关联信息:

【The SIWE plugin adds a walletAddress table to store user wallet associations:】

字段类型描述
id字符串主键
userId字符串关联到 user.id
address字符串以太坊钱包地址
chainId数字链 ID(例如,1 表示以太坊主网)
isPrimary布尔值是否为用户的主要钱包
createdAt日期创建时间戳

示例实现

【Example Implementation】

这是一个完整的示例,展示如何实现 SIWE 认证:

【Here's a complete example showing how to implement SIWE authentication:】

auth.ts
import { betterAuth } from "better-auth";
import { siwe } from "better-auth/plugins";
import { generateRandomString } from "better-auth/crypto";
import { verifyMessage, createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";

export const auth = betterAuth({
  database: {
    // your database configuration
  },
  plugins: [
    siwe({
      domain: "myapp.com",
      emailDomainName: "myapp.com",
      anonymous: false,
      getNonce: async () => {
        // Generate a cryptographically secure random nonce
        return generateRandomString(32, "a-z", "A-Z", "0-9");
      },
      verifyMessage: async ({ message, signature, address }) => {
        try {
          // Verify the signature using viem (recommended)
          const isValid = await verifyMessage({
            address: address as `0x${string}`,
            message,
            signature: signature as `0x${string}`,
          });
          return isValid;
        } catch (error) {
          console.error("SIWE verification failed:", error);
          return false;
        }
      },
      ensLookup: async ({ walletAddress }) => {
        try {
          // Optional: lookup ENS name and avatar using viem
          // You can use viem's ENS utilities here
          const client = createPublicClient({
            chain: mainnet,
            transport: http(),
          });

          const ensName = await client.getEnsName({
            address: walletAddress as `0x${string}`,
          });

          const ensAvatar = ensName
            ? await client.getEnsAvatar({
                name: ensName,
              })
            : null;

          return {
            name: ensName || walletAddress,
            avatar: ensAvatar || "",
          };
        } catch {
          return {
            name: walletAddress,
            avatar: "",
          };
        }
      },
    }),
  ],
});

On this page