使用 Ethereum 登录 (SIWE)

使用 Ethereum 登录 (SIWE) 插件允许用户按照 ERC-4361 标准 使用其 Ethereum 钱包进行身份验证。此插件通过允许您实现自己的消息验证和 nonce 生成逻辑来提供灵活性。

安装

添加服务器插件

将 SIWE 插件添加到您的 auth 配置中:

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

请参阅 架构 部分以手动添加字段。

添加客户端插件

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

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

使用方法

生成 Nonce

在签署 SIWE 消息之前,您需要为钱包地址生成一个 nonce:

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);
}

使用 Ethereum 登录

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

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);
}

特定链示例

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

ethereum-mainnet.ts
// Ethereum 主网 (chainId 可以省略,默认值为 1)
const { data, error } = await authClient.siwe.verify({
  message,
  signature,
  walletAddress,
  // chainId: 1 (default)
});
polygon.ts
// Polygon (chainId 必需)
const { data, error } = await authClient.siwe.verify({
  message,
  signature,
  walletAddress,
  chainId: 137, // Required for Polygon
});
arbitrum.ts
// Arbitrum (chainId 必需)
const { data, error } = await authClient.siwe.verify({
  message,
  signature,
  walletAddress,
  chainId: 42161, // Required for Arbitrum
});
base.ts
// Base (chainId 必需)
const { data, error } = await authClient.siwe.verify({
  message,
  signature,
  walletAddress,
  chainId: 8453, // Required for Base
});

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

配置选项

服务器选项

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

  • domain: 您的应用程序域名(用于 SIWE 消息生成,必需)
  • emailDomainName: 当不使用匿名模式创建用户帐户时的电子邮件域名。默认为来自您的基本 URL 的域名
  • anonymous: 是否允许无需电子邮件的匿名登录。默认为 true
  • getNonce: 为每次登录尝试生成唯一 nonce 的函数。您必须实现此函数以返回加密安全的随机字符串。必须返回 Promise<string>
  • verifyMessage: 验证已签署 SIWE 消息的函数。接收消息详细信息并应返回 Promise<boolean>
  • ensLookup: 可选函数,用于查询 Ethereum 地址的 ENS 名称和头像

客户端选项

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

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
    }),
  ],
});

架构

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

字段类型描述
idstring主键
userIdstring引用 user.id
addressstringEthereum 钱包地址
chainIdnumber链 ID(例如,Ethereum 主网为 1)
isPrimaryboolean是否为用户的主要钱包
createdAtdate创建时间戳

示例实现

以下是一个完整的示例,展示如何实现 SIWE 身份验证:

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);
      },
      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