DEV Community

Cover image for How to use Crypto Wallet for Authentication in ReactJS
Suraj Vishwakarma
Suraj Vishwakarma

Posted on • Edited on • Originally published at surajondev.com

How to use Crypto Wallet for Authentication in ReactJS

Introduction

Authentication is an important aspect of an application to verify the identity of users. We use a combination of email/username with a password for authentication. Managing the password of various websites is a hectic job to do. We can also opt for OAuth for authentication such as Login with Google, FaceBook, Twitter, etc. You do not have to deal with password managers. This power comes with the cons of privacy. The website can request access to a variety of data from the OAuth application.

Web3 is rising and it has a better way of authenticating the user. We can use Crypto Wallets for authentication purposes. Using a crypto wallet for authentication will not require a managing password and your privacy will be protected.

Crypto Wallets in the form of a hard wallet such as a USB stick or a web/mobile application form such as Metamask allow people to exchange coins and tokens. Crypto Wallets have public/private keys. These wallets are protected by password or biometric authentication. To use the crypto wallet, the user needs to sign in to the wallet. As there are unique private keys for every user, we can use these crypto wallets for authenticating the user.

Today, we are going to look into:

  • Connect dApp to Wallet in our case MetaMask
  • Getting a signature from the wallet
  • Verifying signature for authentication

You can follow me on Twitter(@surajondev) as we will explore more authentication techniques in the future. Get the notification as soon as possible.

So, let's get started.

Environment Setup

We are going to use ReactJS for our front-end. React is for building the component-based frontend application.

Here is the command to install React:

npx create-react-app crypto-auth
Enter fullscreen mode Exit fullscreen mode

Delete all the unnecessary files and code.

Dependencies

As for dependencies, we only need one library i.e, ethers. Ethers will be helpful in interacting with our wallet.

cd into the crypto-auth and enter the below command in the terminal:

npm i ethers
Enter fullscreen mode Exit fullscreen mode

Note: To run the above commands, you need to have node.js pre-installed.

Download MetaMask wallet as a chrome extension from here

Now, we are all set for writing code.

App.js

In App.js file, at the top, we have to import the necessary libraries and CSS files. Here we have all the imports:

import "./styles.css";
import { useState } from "react";
import { ethers } from "ethers";
Enter fullscreen mode Exit fullscreen mode

In the functional component having the name App we are going to implement all the functionality of the authentication using the Crypto Wallet.

As for the return statement, that contains the JSX of the component to display on the web page. We are going to have:

  • H1 displaying the title Crypto Auth
  • Button to connect our application to Wallet(MetaMask)
  • Message paragraph for displaying error/info for instance Login failed/ User Login successful
  • If login happens, a paragraph to display the public key of the account

Here is the code:

import "./styles.css";
import { useState } from "react";
import { ethers } from "ethers";

const cryptoButton = async () => {
  const [msg, setMsg] = useState();
  const [accnt, setAccnt] = useState();

  return (
    <div className="App">
      <h1>Crypto Authentication</h1>
      <button className="ConnectBtn" onClick={cryptoButton}>
        Connect Wallet
      </button>
      <p>{msg}</p>
      {msg === "User Login" && <div>Account: {accnt}</div>}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

We have used the cryptoButton function to implement the code for connecting MetaMask.

cryptoButton

First, let's look into the code, and then I will explain each step. Here is the code:

  const cryptoButton = async () => {
    const { ethereum } = window;
    if (ethereum.isMetaMask) {
      setMsg("MetaMask Installed");
      await ethereum.request({ method: "eth_requestAccounts" });
      const accounts = await ethereum.request({ method: "eth_accounts" });

      const provider = new ethers.providers.Web3Provider(ethereum);
      const signer = provider.getSigner();
      const message = randomString(16);
      const signature = await signer.signMessage(message);

      const signAddress = await ethers.utils.verifyMessage(message, signature);
      if (signAddress.toLowerCase() === accounts[0].toLowerCase()) {
        setMsg("User Login");
        setAccnt(accounts[0]);
      } else {
        setMsg("Login failed");
      }
    } else {
      setMsg("MetaMask is not installed");
    }
  };
Enter fullscreen mode Exit fullscreen mode

At the top, we are extracting the ethereum object from the window. This function contains all the information regarding blockchain interaction. If MetaMask is installed, it injects its code into the ethereum object. Now, we have used the ethereum.isMetaMask() function to verify the valid installation of MetaMask. We will progress only if the MetaMask is installed as the browser extension. Otherwise, it will set a message with MetaMask is not installed.

With the next two lines, we are accessing the account details from the wallet. This will make a request to the MetaMask. After that, we are storing the public key from the wallet in the accounts variable. The public key will be in the array format.

await ethereum.request({ method: "eth_requestAccounts" });
const accounts = await ethereum.request({ method: "eth_accounts" });
Enter fullscreen mode Exit fullscreen mode

At this point, we have successfully connected our application with MetaMask. We need to get a signature from the wallet user. As anyone with API can inject the pubic key but the signature require a private key which makes it a valid authentication.

Signature

Here is the code for getting the signature:

      const provider = new ethers.providers.Web3Provider(ethereum);
      const signer = provider.getSigner();
      const message = randomString(16);
      const signature = await signer.signMessage(message);
Enter fullscreen mode Exit fullscreen mode

In the first line, we are getting the provider. A Provider in ethers is a read-only abstraction to access the blockchain data. From the provider, we are accessing the getSigner() function and storing it into the signer variable. We need to pass a message to which the wallet will sign. You can pass a message like "Welcome to Crypto Auth". It will be shown to the user will signing the message.

Message sign

This message is used to create the signature. Complicating the signature will make it harder to decode. So passing a nonce is ideal. A nonce is an arbitrary number that is unique for each user. This nonce can be alphanumeric or big numbers. You can store this nonce for each user in the database. As we are not connecting our application to the database, we need not insure uniqueness, so I have created randomString(16) function to provide us with random alphanumeric values. 16 as an argument is for 16 characters of the string.

Here is the code for the randomString() function:

const randomString = function (length) {
  var text = "";
  var possible =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  for (var i = 0; i < length; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
};
Enter fullscreen mode Exit fullscreen mode

After that, we are getting the signature from the user through the wallet.

Signature Verification

After getting the signature, we need to verify that the public key from the wallet that's signing the signature is the same as the public key that we requested earlier.

In ethers library, there is a function, ethers.utils.verifyMessage(), for verification of signature. This function takes nonce (In our case, we have stored the nonce in the variable having a name message) and signature as the argument. The function returns the public key that has signed the message.

const signAddress = await ethers.utils.verifyMessage(message, signature);
Enter fullscreen mode Exit fullscreen mode

Now, we have two public keys. The first one is from the earlier request to the wallet and the second one is from the signed message. We just need to make sure that both public keys are the same.

if (signAddress.toLowerCase() === accounts[0].toLowerCase()) {
        setMsg("User Login");
        setAccnt(accounts[0]);
} else {
        setMsg("Login failed");
}
Enter fullscreen mode Exit fullscreen mode

We have converted both the public key into lowercase for verification. The message will be displayed according to the verification.

Here is the code of the App.js altogether at once:

import "./styles.css";
import { useState } from "react";
import { ethers } from "ethers";

const randomString = function (length) {
  var text = "";
  var possible =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  for (var i = 0; i < length; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
};

export default function App() {
  const [msg, setMsg] = useState();
  const [accnt, setAccnt] = useState();

  const cryptoButton = async () => {
    const { ethereum } = window;
    if (ethereum.isMetaMask) {
      setMsg("MetaMask Installed");
      await ethereum.request({ method: "eth_requestAccounts" });
      const accounts = await ethereum.request({ method: "eth_accounts" });

      const provider = new ethers.providers.Web3Provider(ethereum);
      const signer = provider.getSigner();
      const message = randomString(16);
      const signature = await signer.signMessage(message);

      const signAddress = await ethers.utils.verifyMessage(message, signature);
      if (signAddress.toLowerCase() === accounts[0].toLowerCase()) {
        setMsg("User Login");
        setAccnt(accounts[0]);
      } else {
        setMsg("Login failed");
      }
    } else {
      setMsg("MetaMask is not installed");
    }
  };
  return (
    <div className="App">
      <h1>Crypto Authentication</h1>
      <button className="ConnectBtn" onClick={cryptoButton}>
        Connect Wallet
      </button>
      <p>{msg}</p>
      {msg === "User Login" && <div>Account: {accnt}</div>}
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Demo

If everything goes well you can see the below screen.

homepage

homepage

Now, if you click on the Connect Wallet button. Your MetaMask will pop up asking for a signature with the message.

sign message

After you click on Sign, you will see the public key from the wallet on the screen.

verified public key

Extending the project

You can extend the project by adding a database in the backend. You can use MongoDB Atlas for database.

Connect With Me

Conclusion

We have successfully created a button that will surely connect your application to the wallet (MetaMask). In this way, you need to worry about the password and all.

I hope, the article has benefitted you with more knowledge. Thanks for reading the blog post.

Top comments (3)

Collapse
 
yongchanghe profile image
Yongchang He

Thank you for sharing this!

Collapse
 
surajondev profile image
Suraj Vishwakarma

Glad to help you!

Collapse
 
stephan_thomas profile image
Stephan Thomas

This is very nice but is there a way to connect other wallets like Solana wallets