next-auth集成以太坊钱包登陆

82 阅读1分钟

Next-auth中可以通过Credentials进行任意凭证登陆,如用户名和密码,那如果通过以太坊钱包进行登陆呢?

基于siwe这个库即可实现

siwe全称:sign in with Ethereum

具体实现

版本:

{
  "next-auth": "5.0.0-beta.15",
  "siwe": "^2.3.2",
}
  1. next-auth配置
 import NextAuth from 'next-auth';
 import Credentials from 'next-auth/providers/credentials';
 import { PrismaAdapter } from '@auth/prisma-adapter';
 import { SiweMessage } from 'siwe';
 import type { SiweMessage as SiweMessageType } from 'siwe';
 
 export const { handlers, auth, signOut, signIn } = NextAuth({
   ...
   providers: [
     Credentials({
       name: 'Ethereum',
       credentials: {
         message: {
           label: 'Message',
           type: 'text',
           placeholder: '0x0',
         },
         signature: {
           label: 'Signature',
           type: 'text',
           placeholder: '0x0',
         },
       },
       async authorize(credentials) {
         const siwe = new SiweMessage(
           JSON.parse(
             credentials?.message as string,
           ) as Partial<SiweMessageType>,
         );
         const result = await siwe.verify({
           signature: credentials?.signature as string,
         });
         if (result.success) {
           return { // 根据自己需要返回
             id: siwe.address,
             name: siwe.address,
           };
         }
         return null;
       },
     }),
   ],
   ...
 });
 
  1. 连接钱包组件
    'use client';
    
    import React from 'react';
    import { getCsrfToken, signIn } from 'next-auth/react';
    import { SiweMessage } from 'siwe';
    import { useAccount, useConnect, useSignMessage } from 'wagmi';
    import { injected } from 'wagmi/connectors';
    
    import { Button } from '@/components/ui/button';
    
    import { PATHS } from '@/constants';
    
    export async function getServerSideProps() {
      return {
        props: {
          csrfToken: await getCsrfToken(),
        },
      };
    }
    export const SignWithWallet = () => {
      const { signMessageAsync } = useSignMessage();
      const { address, isConnected, chain } = useAccount();
      const { connect, status } = useConnect();
    
      const signMsg: () => Promise<void> = React.useCallback(async () => {
        const message = new SiweMessage({
          domain: window.location.host,
          address: address,
          statement: 'Sign in with Ethereum to the app.',
          uri: window.location.origin,
          version: '1',
          chainId: chain?.id,
          nonce: await getCsrfToken(),
        });
        const signature = await signMessageAsync({
          message: message.prepareMessage(),
        });
        await signIn('credentials', {
          message: JSON.stringify(message),
          signature,
          redirectTo: PATHS.ADMIN_HOME,
        });
      }, [address, chain, signMessageAsync]);
    
      React.useEffect(() => {
        if (status === 'success') {
          signMsg().catch(() => ({}));
        }
      }, [status, signMsg]);
    
      const handleLogin = async () => {
        if (!isConnected) {
          connect({ connector: injected() });
          return;
        }
        await signMsg();
      };
    
      return (
        <Button
          variant="default"
          className="!w-full"
          type="button"
          onClick={() => handleLogin()}
        >
          Connect Wallet
        </Button>
      );
    };
    

注:因为是客户端组件,所以getCsrfToken需要从next-auth/react导出

效果如图:

2024-05-22 10.52.22.gif


项目源码:github.com/Young-Jeff/…