この記事の導入は、EIP-4361 Sign-In with Ethereum ルールに従っています
SIWE (Sign-In with Ethereum) は、イーサリアムでユーザーの身元を確認する方法で、トランザクションを開始するウォレットに似ており、ユーザーがウォレットを制御していることを示します。
現在の認証方法は非常に簡単で、ウォレット プラグインの情報に署名するだけで済みます。
この記事で検討する署名シナリオはイーサリアムに関するもので、Solana、SUI などのその他のシナリオはこの記事の範囲外です。
あなたのプロジェクトには SIWE が必要ですか?
SIWE はウォレット アドレスの認証問題を解決するため、次のようなニーズがある場合は SWIE の使用を検討できます。
- Dapp には独自のユーザー システムがあります。
- 照会される情報はユーザーのプライバシーに関連します。
ただし、Dapp が etherscan のようなアプリケーションなど、クエリベースの機能である場合は、SIWE がなくても問題ありません。
Dapp でウォレット経由で接続した後は、私がウォレットの所有権を持っていることになるのではないでしょうか?
はい、しかし完全に正しいわけではありません。フロントエンドの場合、ウォレットを介して接続した後、自分の ID を指定するのは事実ですが、バックエンドのサポートを必要とする一部のインターフェイス呼び出しでは、自分の ID を渡すだけの場合、自分の ID を指定する方法がありません。インターフェース 住所を持っていれば、誰でもあなたの身元を「借りる」ことができます。結局のところ、住所は公開情報です。
SIWEの原則とプロセス
SIWE のプロセスは、ウォレットの接続、署名、ID の取得の 3 つのステップに要約できます。この3つのステップを詳しく紹介します。
ウォレットを接続する
ウォレットの接続は一般的な WEB3 操作です。ウォレット プラグインを介して Dapp にウォレットを接続できます。
サイン
SIWE では、署名の手順には、Nonce 値の取得、ウォレット署名、バックエンド署名検証が含まれます。
Nonce 値を取得するには、ETH トランザクションの Nonce 値の設計を参照する必要があり、Nonce 値を取得するためにバックエンド インターフェイスを呼び出す必要もあります。リクエストを受信した後、バックエンドはランダムな Nonce 値を生成し、それを現在のアドレスに関連付けて、後続の署名に備えます。
フロントエンドは Nonce 値を取得した後、署名コンテンツを構築する必要があります。SIWE が設計できる署名コンテンツには、取得した Nonce 値、ドメイン名、チェーン ID、署名コンテンツなどが含まれます。通常、フロントエンドが提供する署名メソッドを使用します。ウォレットにコンテンツの署名を実行します。
署名が構築された後、署名は最終的にバックエンドに送信されます。
アイデンティティを取得する
バックエンドが署名を検証して渡した後、対応するユーザー ID (JWT にすることができます) を返します。フロントエンドは、バックエンド リクエストの送信時に対応するアドレスと ID を取得することで、ウォレットの所有権を示すことができます。
練習してみよう
開発者がウォレット接続と SIWE に迅速にアクセスできるようにするためのコンポーネントとライブラリがすでに多数あります。これを実践することで、Dapp がユーザー ID 検証のために JWT を返せるようにすることができます。
このデモは SIWE の基本プロセスを紹介するためにのみ使用されており、運用環境で使用するとセキュリティ上の問題が発生する可能性があることに注意してください。
事前に準備する
この記事では nextjs を使用してアプリケーションを開発するため、開発者は nodejs 環境を準備する必要があります。 nextjs を使用する利点の 1 つは、フロントエンド プロジェクトとバックエンド プロジェクトに分割せずに、フルスタック プロジェクトを直接開発できることです。
依存関係をインストールする
まず、nextjs をプロジェクト ディレクトリにインストールします。コマンド ラインから次のように入力します。
npx create-next-app@14
プロンプトに従って nextjs をインストールすると、次のコンテンツが表示されます。
プロジェクト ディレクトリに入ると、nextjs スキャフォールディングが多くの作業を行っていることがわかります。プロジェクト ディレクトリでプロジェクトを実行できます。
npm run dev
その後、ターミナルのプロンプトに従ってlocalhost: 3000
と入力すると、基本的な nextjs プロジェクトが実行されていることがわかります。
SIWE 関連の依存関係をインストールする
前の紹介によると、SIWE はログイン システムに依存する必要があるため、プロジェクトをウォレットに接続する必要があります。次の理由から、ここでは Ant Design Web3 ( https://web3.ant.design/ ) を使用します。
- 完全に無料で、現在積極的にメンテナンス中です
- WEB3 コンポーネント ライブラリとして、その使用エクスペリエンスは通常のコンポーネント ライブラリと同様であり、追加の精神的負担はありません。
- そしてSIWEをサポートします。
ターミナルに次のように入力する必要があります。
npm install antd @ant-design/web3 @ant-design/web3-wagmi wagmi viem @tanstack/react-query --save
ワグミの紹介
Ant Design Web3 の SIWE は Wagmi ライブラリに依存して実装されるため、関連するコンポーネントをプロジェクトに導入する必要があります。 Wagmi が提供する Hook をプロジェクト全体で使用できるように、 layout.tsx
に対応する Provider を導入します。
最初に WagmiProvider の構成を定義します。コードは次のとおりです。
"use client"; import { getNonce, verifyMessage } from "@/app/api"; import { Mainnet, MetaMask, OkxWallet, TokenPocket, WagmiWeb3ConfigProvider, WalletConnect, } from "@ant-design/web3-wagmi"; import { QueryClient } from "@tanstack/react-query"; import React from "react"; import { createSiweMessage } from "viem/siwe"; import { http } from "wagmi"; import { JwtProvider } from "./JwtProvider"; const YOUR_WALLET_CONNECT_PROJECT_ID = "c07c0051c2055890eade3556618e38a6"; const queryClient = new QueryClient(); const WagmiProvider: React.FC = ({ children }) => { const [jwt, setJwt] = React.useState(null); return ( (await getNonce(address)).data, createMessage: (props) => { return createSiweMessage({ ...props, statement: "Ant Design Web3" }); }, verifyMessage: async (message, signature) => { const jwt = (await verifyMessage(message, signature)).data; setJwt(jwt); return !!jwt; }, }} chains={[Mainnet]} transports={{ [Mainnet.id]: http(), }} walletConnect={{ projectId: YOUR_WALLET_CONNECT_PROJECT_ID, }} wallets={[ MetaMask(), WalletConnect(), TokenPocket({ group: "Popular", }), OkxWallet(), ]} queryClient={queryClient} > {children} ); }; export default WagmiProvider;
Ant Design Web3 が提供するプロバイダーを使用し、SIWE のいくつかのインターフェイスを定義しました。特定のインターフェイスの実装については後ほど紹介します。
後ほど、ウォレットに接続するためのボタンを導入し、フロントエンドに接続入口を追加できるようにします。
この時点ですでに SIWE に接続している場合でも、手順は非常に簡単です。
その後、ウォレットと署名を接続するための接続ボタンを定義する必要があります。コードは次のとおりです。
"use client"; import type { Account } from "@ant-design/web3"; import { ConnectButton, Connector } from "@ant-design/web3"; import { Flex, Space } from "antd"; import React from "react"; import { JwtProvider } from "./JwtProvider"; export default function App() { const jwt = React.useContext(JwtProvider); const renderSignBtnText = ( defaultDom: React.ReactNode, account?: Account ) => { const { address } = account ?? {}; const ellipsisAddress = address ? `${address.slice(0, 6)}...${address.slice(-6)}` : ""; return `Sign in as ${ellipsisAddress}`; }; return ( <>
{jwt}
); }
このようにして、最も単純な SIWE ログイン フレームワークを実装しました。
インターフェースの実装
上記の紹介によると、SIWE にはバックエンドによるユーザーの身元確認を支援するためにいくつかのインターフェイスが必要です。それでは簡単に実装してみましょう。
ノンス
Nonce の目的は、ウォレットによって生成される署名の内容を署名のたびに変更できるようにして、署名の信頼性を向上させることです。検証の精度を向上させるために、この Nonce の生成をユーザーが渡したアドレスに関連付ける必要があります。
Nonce の実装は非常に簡単で、まずランダムな文字列 (文字と数字から生成) を生成し、次にその nonce をアドレスに接続します。
import { randomBytes } from "crypto"; import { addressMap } from "../cache"; export async function GET(request: Request) { const { searchParams } = new URL(request.url); const address = searchParams.get("address"); if (!address) { throw new Error("Invalid address"); } const nonce = randomBytes(16).toString("hex"); addressMap.set(address, nonce); return Response.json({ data: nonce, }); }
サインメッセージ
SignMessage の機能はコンテンツに署名することです。通常、この部分はウォレット プラグインを通じて完了します。このデモでは、Wagmi の署名メソッドを指定するだけで済みます。使用済み。
verifyメッセージ
ユーザーがコンテンツに署名した後、署名前のコンテンツと署名を検証のためにバックエンドに送信する必要があります。バックエンドは、署名から対応するコンテンツを解析して比較します。
さらに、署名されたコンテンツのナンス値がユーザーに送信されたものと一致するかどうかなど、署名されたコンテンツに対していくつかのセキュリティ検証を行う必要があります。検証に合格した後、その後の権限検証のために、対応するユーザー JWT を返す必要があります。サンプル コードは次のとおりです。
import { createPublicClient, http } from "viem"; import { mainnet } from "viem/chains"; import jwt from "jsonwebtoken"; import { parseSiweMessage } from "viem/siwe"; import { addressMap } from "../cache"; const JWT_SECRET = "your-secret-key"; // 请使用更安全的密钥,并添加对应的过期校验等const publicClient = createPublicClient({ chain: mainnet, transport: http(), }); export async function POST(request: Request) { const { signature, message } = await request.json(); const { nonce, address = "0x" } = parseSiweMessage(message); console.log("nonce", nonce, address, addressMap); // 校验nonce 值是否一致if (!nonce || nonce !== addressMap.get(address)) { throw new Error("Invalid nonce"); } // 校验签名内容const valid = await publicClient.verifySiweMessage({ message, address, signature, }); if (!valid) { throw new Error("Invalid signature"); } // 生成jwt 并返回const token = jwt.sign({ address }, JWT_SECRET, { expiresIn: "1h" }); return Response.json({ data: token, }); }
この時点で、基本的に SIWE ログインを実装する Dapp が開発されました。
一部の最適化項目
SIWE にログインするときに、デフォルトの RPC ノードを使用すると、検証プロセスに 30 秒近くかかるため、インターフェイスの応答時間を改善するために専用ノード サービスを使用することを強くお勧めします。この記事では、ZAN のノード サービス ( https://zan.top/home/node-service?chInfo=ch_WZ ) を使用します。ZAN ノード サービス コンソールに移動して、対応する RPC 接続を取得できます。
Ethereum メイン ネットワークへの HTTPS RPC 接続を取得した後、コード内のpublicClient
のデフォルト RPC を置き換えます。
const publicClient = createPublicClient({ chain: mainnet, transport: http('https://api.zan.top/node/v1/eth/mainnet/xxxx'), //获取到的ZAN 节点服务RPC });
交換後は検証時間が大幅に短縮され、インターフェース速度が大幅に高速化されます。