SIWE User Manual: How to make your Dapp more powerful?

Reprinted from panewslab
12/31/2024·4MThe introduction of this article follows the EIP-4361 Sign-In with Ethereum rules
SIWE (Sign-In with Ethereum) is a method of verifying user identity on Ethereum. It is similar to a wallet initiating a transaction, indicating that the user has control over the wallet.
The current authentication method is very simple. You only need to sign the information in the wallet plug-in. Common wallet plug-ins already support it.
The signature scenario considered in this article is on Ethereum, and others like Solana, SUI, etc. are beyond the scope of this article.
Does your project require SIWE?
SIWE is to solve the authentication problem of wallet addresses, so if you have the following needs, you can consider using SWIE:
* Your Dapp has its own user system;
* The information to be queried is related to user privacy;
But if your Dapp is a query-based function, such as an application like etherscan, it is okay without SIWE.
You may have a question. After I connect through the wallet on the Dapp, doesn't it mean that I have ownership of the wallet?
Yes, but not entirely right. For the front end, it is true that after you connect through the wallet, you indicate your identity, but for some interface calls that require back-end support, you have no way to indicate your identity. If you just pass your identity in the interface If you have an address, then anyone can "borrow" your identity. After all, the address is public information.
SIWE Principles and Processes
SIWE's process can be summed up in three steps: connect wallet--sign--obtain identity. We introduce these three steps in detail.
Connect wallet
Connecting a wallet is a common WEB3 operation. You can connect your wallet in Dapp through a wallet plug-in.
sign
In SIWE, the steps of signing include obtaining the Nonce value, wallet signature and backend signature verification.
Obtaining the Nonce value should refer to the design of the Nonce value in ETH transactions, and it also needs to call the back-end interface to obtain it. After receiving the request, the backend generates a random Nonce value and associates it with the current address to prepare for subsequent signatures.
After the front end obtains the Nonce value, it needs to construct the signature content. The signature content that SIWE can design includes the obtained Nonce value, domain name, chain ID, signature content, etc. We generally use the signature method provided by the wallet to perform the content sign.
After the signature is constructed, the signature is finally sent to the backend.
getgetidentity
After the backend verifies the signature and passes it, it will return the corresponding user identity, which can be a JWT. The frontend can then indicate its ownership of the wallet by bringing the corresponding address and identity when sending the backend request.
Practice it
There are already many components and libraries that support developers to quickly access wallet connections and SIWE. We can practice it. The goal of practice is to enable your Dapp to return JWT for user identity verification.
Note that this DEMO is only used to introduce the basic process of SIWE, and there may be security issues when used in a production environment.
Prepare in advance
This article uses nextjs to develop applications, so developers need to prepare the nodejs environment. One benefit of using nextjs is that we can directly develop full-stack projects without splitting into front-end and back-end projects.
Install dependencies
First we install nextjs. In your project directory, enter from the command line:
npx create-next-app@14
After installing nextjs according to the prompts, you can see the following content:
After entering the project directory, you can see that the nextjs scaffolding has done a lot of work for us. We can run the project in the project directory:
npm run dev
Afterwards, according to the terminal prompts, enter localhost: 3000
and you
can see that a basic nextjs project is running.
Install SIWE related dependencies
According to the previous introduction, SIWE needs to rely on the login system, so we need to connect our project to the wallet. Here we use Ant Design Web3 ( https://web3.ant.design/ ) because:
- It's completely free and is currently actively maintained
- As a WEB3 component library, its usage experience is similar to that of ordinary component libraries, without any additional mental burden.
- And supports SIWE.
We need to enter in the terminal:
npm install antd @ant-design/web3 @ant-design/web3-wagmi wagmi viem @tanstack/react-query --save
Introducing Wagmi
Ant Design Web3's SIWE relies on the Wagmi library to implement, so relevant
components need to be introduced in the project. We introduce the
corresponding Provider in layout.tsx
so that the entire project can use the
Hooks provided by Wagmi.
We first define the configuration of WagmiProvider, the code is as follows:
\"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;
We used the Provider provided by Ant Design Web3 and defined some interfaces of SIWE. We will introduce the implementation of the specific interfaces later.
Later, we will introduce a button to connect to the wallet, so that a connection entrance can be added to the front end.
Even if you have already connected to SIWE at this point, the steps are very simple.
After that, we need to define a connection button to connect the wallet and signature. The code is as follows:
\"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}
); }
In this way, we have implemented the simplest SIWE login framework.
Interface implementation
According to the above introduction, SIWE needs some interfaces to help the backend verify the user's identity. Now let's implement it simply.
Nonce
The purpose of Nonce is to allow the signature content generated by the wallet to change each time it is signed, thereby improving the reliability of the signature. The generation of this Nonce needs to be associated with the address passed in by the user to improve the accuracy of verification.
The implementation of Nonce is very straightforward. First we generate a random string (generated from letters and numbers), and then connect the nonce to the address. The code is as follows:
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
The function of signMessage is to sign the content. This part of the function is usually completed through the wallet plug-in. We generally do not need to configure it, we only need to specify the method. In this demo, Wagmi's signature method is used.
verifyMessage
After the user signs the content, the content before signing and the signature need to be sent to the backend for verification. The backend parses the corresponding content from the signature for comparison. If they are consistent, the verification is passed.
In addition, some security verification needs to be done for the signed content, such as whether the Nonce value in the signed content is consistent with the one we send to the user. After passing the verification, the corresponding user JWT needs to be returned for subsequent permission verification. The sample code is as follows:
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, }); }
At this point, a Dapp that basically implements SIWE login has been developed.
Some optimization items
Now when we log in to SIWE, if we use the default RPC node, the verification process will take nearly 30 seconds, so it is strongly recommended to use a dedicated node service to improve the response time of the interface. This article uses ZAN's node service ( https://zan.top/home/node-service?chInfo=ch_WZ ). You can go to the ZAN node service console to obtain the corresponding RPC connection.
After we obtain the HTTPS RPC connection to the Ethereum main network, we
replace the default RPC of publicClient
in the code:
const publicClient = createPublicClient({ chain: mainnet, transport: http(\'https://api.zan.top/node/v1/eth/mainnet/xxxx\'), //获取到的ZAN 节点服务RPC });
After replacement, the verification time can be significantly reduced and the interface speed is significantly accelerated.