Create An NFT Gated Website

Create An NFT Gated Website - thirdweb Guides

In this guide, we'll show you how to create a website that restricts content based on owning an NFT with React.

You can use any ERC-721, ERC-1155 or even ERC-1155 contract to enable token gating using the website we are creating.

Creating an app

💡
You can also clone the GitHub repository associated with this guide.

To get started, we can use the CLI to clone a starter kit.

npx thirdweb create app --next --js

Setup dApp details

Create a new folder named const under the project. Under this new folder, create a new file called yourDetails.js. This file will contain all the information such as your smart contract address and minimum numbers of NFTs required to access protected content:

// Replace this with your contract address
export const contractAddress = "0xB35cE7f1Be137a207e8D959B52767bEabbccE46d";

// Replace this with your domain name.
export const domainName = "example.com";

// Minimum number of tokens required to access
export const minimumBalance = 1;

// Token ID for ERC-1155 tokens
export const erc1155TokenId = 0;

Change the erc1155TokenId if you are using an ERC-1155 token. domainName is used for thirdweb authentication. Make sure to replace it with your domain.

Setup the ThirdwebProvider

Inside the pages/_app.js page, we wrap our application in the ThirdwebProvider component to access all of the React SDK's hooks anywhere in our application.

In this file, we need to configure an activeChain that our project will work on.

For this project, we’ll be using Polygon Mumbai since our contract is on the Mumbai testnet. Feel free to change it to any network you desire.

import { ChainId, ThirdwebProvider } from "@thirdweb-dev/react";
import Head from "next/head";
import ThirdwebGuideFooter from "../components/ThirdwebGuideFooter";
import { domainName } from "../const/yourDetails";
import "../styles/globals.css";

// This is the chainId your dApp will work on.
const activeChain = "mumbai";

function MyApp({ Component, pageProps }) {
  return (
    <ThirdwebProvider
      activeChain={activeChain}
      authConfig={{
        domain: domainName,
        authUrl: "/api/auth",
      }}
    >
      <Head>
        <title>NFT Gated Website</title>
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta
          name="description"
          content="Learn how to use the thirdweb Auth SDK to create an NFT Gated Website"
        />
      </Head>
      <Component {...pageProps} />
      <ThirdwebGuideFooter />
    </ThirdwebProvider>
  );
}

export default MyApp;

Creating the Auth Config File

Create a new file called auth.config.js at the root of your project.

Here is where we configure the ThirdwebAuth object for us to use in our authentication endpoints.

We need to pass two configuration options to the ThirdwebAuth object:

  1. You can store our wallet's private key in the environment variable during development. However, it's not recommended to store private keys in environment variables during production.
  2. Our domain name, which can be any value you want, typically the name of your website such as thirdweb.com.
import { ThirdwebAuth } from "@thirdweb-dev/auth/next";
import { PrivateKeyWallet } from "@thirdweb-dev/auth/evm";
import { domainName } from "./const/yourDetails";

export const { ThirdwebAuthHandler, getUser } = ThirdwebAuth({
  domain: domainName,
  wallet: new PrivateKeyWallet(process.env.THIRDWEB_AUTH_PRIVATE_KEY || ""),
});
💡
Learn how to export your private key from your wallet.
⚠️
Never commit any file containing your private key to your source control. Learn how to securely access your private key.

Auth API Endpoint

Finally, we have a catch-all API route
called pages/api/auth/[...thirdweb].js, which exports the ThirdwebAuthHandler to manage all of the required auth endpoints like login and logout.

Create a new page inside the pages/api/auth folder (you'll need to create this) and name it [...thirdweb].js,
and export the ThirdwebAuthHandler from this file that we set up in our auth.config.js file.

import { ThirdwebAuthHandler } from "../../../auth.config";

export default ThirdwebAuthHandler();

Restricting Home Page Access

On the homepage, we run server-side code within getServersideProps to check:

  1. The user is authenticated
  2. The user has an NFT from our collection

If the user isn't authenticated or doesn't have an NFT, they're redirected to the /login page before they can access
the content of the home page.

Let's set up this home page logic now.

First, we need to import the functionality we're going to be using:

import React, { useEffect } from "react";
import { ThirdwebSDK } from "@thirdweb-dev/sdk";
import { useLogout, useUser } from "@thirdweb-dev/react";
import { getUser } from "../auth.config";
import checkBalance from "../util/checkBalance";
import styles from "../styles/Home.module.css";
import { useRouter } from "next/router";

Since our home page is only accessible by authenticated NFT holders, we can show any data here, such as a thank you message

export default function Home() {
  const { logout } = useLogout();
  const { isLoggedIn, isLoading } = useUser();
  const router = useRouter();

  useEffect(() => {
    if (!isLoading && !isLoggedIn) {
      router.push("/login");
    }
  }, [isLoading, isLoggedIn, router]);

  return (
    <div className={styles.container}>
      <h1 className={styles.h1}>Restricted Access Page</h1>
      <p className={styles.explain}>
        Thanks for being a member of our NFT community!
      </p>

      <button className={styles.mainButton} onClick={logout}>
        Logout
      </button>
    </div>
  );
}

Above, we're showing a simple thank you message and a button to logout, which will sign them out and redirect the user to the /login page.

Before we reach this point, we'll run logic inside out getServersideProps function,
which checks the user's authentication status and their NFT balance.

If they are not authenticated or don't own an NFT, they are redirected back to the /login page.

// This gets called on every request
export async function getServerSideProps(context) {
  const user = await getUser(context.req);

  if (!user) {
    return {
      redirect: {
        destination: "/login",
        permanent: false,
      },
    };
  }

  // Ensure we are able to generate an auth token using our private key instantiated SDK
  const PRIVATE_KEY = process.env.THIRDWEB_AUTH_PRIVATE_KEY;
  if (!PRIVATE_KEY) {
    throw new Error("You need to add an PRIVATE_KEY environment variable.");
  }

  // Instantiate our SDK
  const sdk = ThirdwebSDK.fromPrivateKey(
    process.env.THIRDWEB_AUTH_PRIVATE_KEY,
    "mumbai"
  );

  // Check to see if the user has an NFT
  const hasNft = await checkBalance(sdk, user.address);

  // If they don't have an NFT, redirect them to the login page
  if (!hasNft) {
    console.log("User", user.address, "doesn't have an NFT! Redirecting...");
    return {
      redirect: {
        destination: "/login",
        permanent: false,
      },
    };
  }

  // Finally, return the props
  return {
    props: {},
  };
}

Checking NFT Balance

In the snippet above, we call a utility function called checkBalance, which checks to see if the user has an NFT from our collection.

import { isFeatureEnabled } from "@thirdweb-dev/sdk";
import {
  contractAddress,
  erc1155TokenId,
  minimumBalance,
} from "../const/yourDetails";

export default async function checkBalance(sdk, address) {
  const contract = await sdk.getContract(
    contractAddress // replace this with your contract address
  );

  let balance;

  if (isFeatureEnabled(contract.abi, "ERC1155")) {
    balance = await contract.erc1155.balanceOf(address, erc1155TokenId);
  } else if (isFeatureEnabled(contract.abi, "ERC721")) {
    balance = await contract.erc721.balanceOf(address);
  } else if (isFeatureEnabled(contract.abi, "ERC20")) {
    balance = (await contract.erc20.balanceOf(address)).value;
    return balance.gte((minimumBalance * 1e18).toString());
  }

  // gte = greater than or equal to
  return balance.gte(minimumBalance);
}

The above snippet detects what type of contract contractAddress is and checks the balance accordingly.

Great! Now each time a user attempts to reach the / page, we'll check to see if they have an NFT before allowing them to enter.

Let's create the /login page now where users can sign into our application with their wallet.

Login Page

Create a new page in the pages folder called login.jsx.

This page needs to contain a button that calls the login function

First, let's import the logic we need for this file:

import { ConnectWallet, useAddress } from "@thirdweb-dev/react";
import Link from "next/link";
import styles from "../styles/Home.module.css";

Beneath the imports, we render some conditional logic based on the address variable (which detects the connected wallet address to our site).

We render the ConnectWallet button that is responsible for connecting the user's wallet to the app and also signing in using auth.

This asks the user to sign a message with their wallet to prove their identity,
and redirects them to the URL we configured for the loginRedirect field in the ThirdwebProvider; which we set as /.

This means that after the user approves the message, they will be redirected to the / page; where they will be checked to see if they have an NFT again!

export default function Login() {
  const address = useAddress(); // Get the user's address

  return (
    <div className={styles.container}>
      <h1 className={styles.h1}>Auth - NFT Gated Content</h1>
      <p className={styles.explain}>
        Serve exclusive content to users who own an NFT from your collection,
        using{" "}
        <b>
          <a
            href="https://portal.thirdweb.com/building-web3-apps/authenticating-users"
            target="_blank"
            rel="noopener noreferrer"
            className={styles.purple}
          >
            Auth
          </a>
        </b>
        !
      </p>

      <p className={styles.explain}>
        You cannot access the{" "}
        <Link className={styles.purple} href="/">
          main page
        </Link>{" "}
        unless you own an NFT from our collection!
      </p>

      <hr className={styles.divider} />

      <>
        {address ? (
          <p>
            Welcome, {address?.slice(0, 6)}...{address?.slice(-4)}
          </p>
        ) : (
          <p>Please connect your wallet to continue.</p>
        )}

        <ConnectWallet accentColor="#F213A4" />
      </>
    </div>
  );
}

Full Project

You can create a copy of this project from our example repository.

To create a project with all of the code from this guide, you can run:

npx thirdweb create --template nft-gated-website

Join our Discord to stay up to date with the latest updates from the team!

If you have any feedback regarding thirdweb, please let us know at our feedback website.