使用以太坊登录 (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 插件添加到你的认证配置中:
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 migratenpx @better-auth/cli generateSee the Schema section to add the fields manually.
添加客户端插件
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:】
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:】
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 (chainId can be omitted, defaults to 1)
const { data, error } = await authClient.siwe.verify({
message,
signature,
walletAddress,
// chainId: 1 (default)
});// Polygon (chainId REQUIRED)
const { data, error } = await authClient.siwe.verify({
message,
signature,
walletAddress,
chainId: 137, // Required for Polygon
});// Arbitrum (chainId REQUIRED)
const { data, error } = await authClient.siwe.verify({
message,
signature,
walletAddress,
chainId: 42161, // Required for Arbitrum
});// 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:】
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:】
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: "",
};
}
},
}),
],
});