Sign In with RainbowKit

This tutorial will teach you how to add secure Web3 Moralis authentication to your NextJS application by walking you through the task of creating a full-stack Web3 authentication solution using the popular NextJS framework.

985985

Result of Web3 Moralis Authentication with RainbowKit

Before Starting

You can start this tutorial if you already have a NextJS dapp with MetaMask sign-in functionality.

RainbowKit Installation

npm install @rainbow-me/rainbowkit
yarn add @rainbow-me/rainbowkit
pnpm add @rainbow-me/rainbowkit

RainbowKit Configuration

Modify pages/_app.jsx:

import { createClient, configureChains, defaultChains, WagmiConfig } from 'wagmi';
import { publicProvider } from 'wagmi/providers/public';
import { SessionProvider } from 'next-auth/react';
import { getDefaultWallets, RainbowKitProvider } from '@rainbow-me/rainbowkit';
import '@rainbow-me/rainbowkit/styles.css';

const { provider, webSocketProvider, chains } = configureChains(defaultChains, [publicProvider()]);

const { connectors } = getDefaultWallets({
    appName: 'My RainbowKit App',
    chains,
});

const client = createClient({
    provider,
    webSocketProvider,
    autoConnect: true,
    // added connectors from rainbowkit
    connectors,
});

// added RainbowKitProvider wrapper
function MyApp({ Component, pageProps }) {
    return (
        <WagmiConfig client={client}>
            <SessionProvider session={pageProps.session} refetchInterval={0}>
                <RainbowKitProvider chains={chains}>
                    <Component {...pageProps} />
                </RainbowKitProvider>
            </SessionProvider>
        </WagmiConfig>
    );
}

export default MyApp;

Authentication with RainbowKit

The logic we're achieving works as this. A user connects his wallet using ConnectButton from rainbowkit. Once the wallet is connected, we get address and chain from the following wagmi hooks: useAccount() and useNetwork(). In case the user is not authenticated, we will start the authentication flow (request and sign message).

  1. Open the pages/signin.jsx file and replace the old Authenticate via MetaMask button with <ConnectButton /> from @rainbow-me/rainbowkit:
import { ConnectButton } from '@rainbow-me/rainbowkit';
...

return (
  <div>
    <h3>Web3 Authentication</h3>
    <ConnectButton />
  </div>
);
...
  1. Edit handleAuth() and move it under useEffect():
...
useEffect(() => {
  const handleAuth = async () => {
    const userData = { address, chain: chain.id, network: 'evm' }

    const { data } = await axios.post('/api/auth/request-message', userData, {
      headers: {
        'content-type': 'application/json',
      },
    })

    const message = data.message

    const signature = await signMessageAsync({ message })

    // redirect user after success authentication to '/user' page
    const { url } = await signIn('credentials', {
      message,
      signature,
      redirect: false,
      callbackUrl: '/user',
    })
    /**
     * instead of using signIn(..., redirect: "/user")
     * we get the url from callback and push it to the router to avoid page refreshing
     */
    push(url)
  }
  // if is not authnenticated, but wallet is connected we request a signing message and sign it
  if (status === 'unauthenticated' && isConnected) {
    handleAuth()
  }
}, [status, isConnected])
...
  1. Update missing imports and add new hooks. This is the final code of pages/signin.jsx:
import { ConnectButton } from '@rainbow-me/rainbowkit'
import { signIn, useSession } from 'next-auth/react'
import { useAccount, useSignMessage, useNetwork } from 'wagmi'
import { useEffect } from 'react'
import { useRouter } from 'next/router'
import axios from 'axios'

function SignIn() {
  const { isConnected, address } = useAccount()
  const { chain } = useNetwork()
  const { status } = useSession()
  const { signMessageAsync } = useSignMessage()
  const { push } = useRouter()

  useEffect(() => {
    const handleAuth = async () => {
      const userData = { address, chain: chain.id, network: 'evm' }

      const { data } = await axios.post('/api/auth/request-message', userData, {
        headers: {
          'content-type': 'application/json',
        },
      })

      const message = data.message

      const signature = await signMessageAsync({ message })

      // redirect user after success authentication to '/user' page
      const { url } = await signIn('credentials', {
        message,
        signature,
        redirect: false,
        callbackUrl: '/user',
      })
      /**
       * instead of using signIn(..., redirect: "/user")
       * we get the url from callback and push it to the router to avoid page refreshing
       */
      push(url)
    }
    if (status === 'unauthenticated' && isConnected) {
      handleAuth()
    }
  }, [status, isConnected])

  return (
    <div>
      <h3>Web3 Authentication</h3>
      <ConnectButton />
    </div>
  )
}

export default SignIn

Testing the RainbowKit Connector

Visit http://localhost:3000/signin to test authentication.

  1. Click on Connect Wallet:
985985

Sign In Page

  1. Select and connect a wallet you want to use for authentication from the RainbowKit modal:
985985

RainbowKit Modal

  1. Sign the message:
985985

Signing the Message with MetaMask

  1. After successful authentication, you will be redirected to the /user page:
985985

User Page

  1. Visit http://localhost:3000/user to test the user session's functionality:
  • When a user is authenticated, we show the user's info on the page.
  • When a user is not authenticated, we redirect to the /signin page.
  • When a user is authenticated, we show the user's info on the page, even refreshing after the page. (Explanation: After Web3 wallet authentication, the next-auth library creates a session cookie with an encrypted JWT [JWE] stored inside. It contains session info [such as an address and signed message] in the user's browser.)

Did this page help you?