The complete guide to Web3 authentication

Authentication is hard. Sessions, JWT, 2FA, Single Sign-on... Solving this challenge is so hard that companies it has made the companies solving it worth billions. At the core of this difficult coding problem, we find something of great value: user data.

Your data as a user is worth a lot of money. This is the foundation of the web2 economy: interacting on the different platforms allows companies to tailor advertising and fuel the consumption economy. Web3 technologies embrace a different mentality, one based on owning your own data anonymously. This means we have to rethink authentication when it comes to Web3.

This post will explore the building blocks of Web3 authentication and explore the best practices around it.

Building Blocks

Web3 authentication moves the location of user-based data in the open. Most user data lives on the blockchain, let's examine the different parts:

  • Wallets: The term wallet is confusing for a number of reasons. It leads us to think that we own coins and assets inside it. This wallet is instead software that is built to let you interact with the blockchain. These assets are instead managed on the blockchain itself.
  • Your Address: is the way that the blockchain publicly identifies you. It is created by the wallet using a private key that only you should own. This key is used to derive your address and approve transactions.
  • Approving transactions: approving a transaction means that your wallet will use your private key to perform a cryptographic signature. In simple terms, you are signing that you agree to a transaction to happen. This is almost the same as using your PIN at the ATM. When you approve a transaction, it will be submitted to the blockchain.
  • Signing a message: this is really similar to approving a transaction but you are simply digitally signing a message that can be safely validated. Nothing actually happens on the blockchain when signing a message.

Phew! That's a lot to think about. Armed with this new information, we come to an understanding:

In Web3 authentication is fully controlled by the user's wallet.

In order to create a good Web3 site, we will need to focus 100% of our development effort around respecting their privacy, users have full control over their wallets and we will need to constantly listen to the state of it to provide good UX.

Any interactivity with your application will mean getting permission from the user to connect, approve or sign data. This means we have to understand the different wallets that we will need to interact with. The different wallets providers are all different but there is a growing effort to standardize the API they offer. Let's explore the main ones.

Popular wallets

  • Metamask is the most commonly supported wallet out there. It works by injecting an ethereum object in the global window. Most popular npm packages support it.
  • Wallet Connect is a protocol that allows wallet applications to establish a secure connection to applications. the most common way to use it is by scanning a bar code to link to mobile phone wallets.
  • Coinbase Wallet is the official wallet of a really popular cryptocurrency exchange. They are available either as a browser extension or a mobile application.

The more wallets you support the more users can get!

Typically, popular applications will look to support all of these wallets. More experienced web3 users will typically be happy with connecting via Metamask but it is better to support the most wallets you can.

Connection flows

Now that we know which wallets are popular and that we should support them, we can try and understand what will be useful for our applications. Typically we will need to make sure about a couple of things before we allow a user to interact with our web3 application:

  • Users approve connecting their wallets
  • They are using the right Blockchain network
  • Have them sign a message allowing them use our services (optional)

Let's explore a full React hook doing the first two steps using the Wagmi Library:

import { useEffect, useState } from "react";
import { useAccount, useConnect, useNetwork } from "wagmi";
import { utils } from "ethers";
import { usePrevious } from "./usePrevious";

export function usePrevious(value) {
  const ref = useRef();

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}

export const useUserState = (route?) => {
  // wagmi hooks that handle talking to metamask
  const [{ data }, disconnect] = useAccount();
  const [{ data: connectData, error: connectError }, connect] = useConnect();
  const [{ data: networkData, error: networkError }, switchNetwork] =
    useNetwork();

  // states that are used to control the UI
  const [state, setState] = useState("notConnected");
  const [loading, setLoading] = useState(false);
  const [isSwitching, setIsSwitching] = useState(false);

  const previousWalletState = usePrevious(data?.address);
  const prevUserState = usePrevious(state);

  const shouldSwitchNetwork =
    state === "wrongNetwork" && loading && !isSwitching;

  // effects watching wagmi state to reflect the UI
  useEffect(() => {
    if (!window.ethereum) {
      setState("noMetamask");
      return;
    }

    // wagmi starts undefined, so we need to wait for it to hydrate
    if (previousWalletState && !data?.address) {
      setState("notConnected");
      return;
    }

    if (data?.address && networkData.chain?.name === "Rinkeby") {
      setState("connected");
      return;
    }
    // if we're connected the next step is to check if we're on the right network
    if (data?.address && networkData.chain?.name !== "Rinkeby") {
      setState("wrongNetwork");
      return;
    }
  }, [data?.address, networkData.chain?.name]);

  useEffect(() => {
    if (connectError || networkError) {
      setLoading(false);
    }
  }, [connectError, networkError]);

  // effects controlling flows after the login
  useEffect(() => {
    if (shouldSwitchNetwork) {
      handleSwitchNetwork();
    }
  }, [state, prevUserState]);

  const handleSwitchNetwork = async () => {
    if (switchNetwork) {
      setIsSwitching(true);
      await switchNetwork(4);
      setIsSwitching(false);
    }
  };

  const handleConnect = async () => {
    setLoading(true);
    if (state === "notConnected") {
      try {
        await connect(connectData?.connectors[0]);
      } catch (e) {
        setLoading(false);
      }
    }

    if (state === "wrongNetwork") {
      await handleSwitchNetwork();
      return;
    }
  };

  const handleDisconnect = () => {
    disconnect();
  };

  return {
    handleConnect,
    handleDisconnect,
    userState: state,
    loading,
  };
};

As we can see, even implementing a simple 1 click button to handle the network and connection request is quite heavy code! In this snippet, we encourage the user actively to switch to the Rinkeby test network. Test networks are a great place to develop your applications. They provide you with free test cryptocurrencies in order to ensure your applications are working well.

UX best practices for web3 authentication.

We might now have a single function to connect and disconnect our users! This is great but we should still ensure our users have a great experience when it comes to using our website and connecting to it.

Don't force your users to connect too quickly!

Some sites tend to separate the app and landing page part of their Web3 sites, forcing users to authenticate before even interacting with the application. This should be avoided! Web3 is meant to be open and inclusive. Do not aggressively request your user's to authenticate. Wait until they want to interact with the site!

A few other cues to provide a great user experience while authenticating users:

  • Make your connect button visible and accessible. Users cannot request on their own a connection.
  • Avoid layout shifts. Use skeletons and loading states while your users are connecting.
  • Be active in catching authentication errors, wallets are a recent piece of technology, errors happen often!
  • Communicate and explain what you are requesting from your users. Web3 users value their privacy.

User experience matters a great deal even in Web3!

Wrapping up.

Web3 authentication is a big topic. It's evolving every day. This is why I created this blog. I am dedicated to making it easy to work in the web3 field. I am uploading every week new web3 examples to help you get started. You can always support this effort by following me on Twitter!