Create an NFT Gated Website on Solana

Create an NFT Gated Website on Solana - thirdweb Guides

In this guide, we'll show you how to create an app where users can sign in with their Solana wallets and get access to a protected page if they own an nft from our collection!

Before we get started, below are some helpful resources where you can learn more about the tools we will use in this guide.

Let's get started.

Setup

Creating An NFT Drop Program

You can follow this guide to deploy an NFT Drop Program onto the Solana network if you haven't done so already!

Creating Next.js App

I am going to use the Next typescript solana starter template for this guide.

If you are following along with the guide, you can create a Next solana project with the thirdweb CLI:

npx thirdweb@latest create app
Create a new app using the thirdweb CLI

If you already have a Next.js app you can simply follow these steps to get started:

  • Install the thirdweb SDKs: @thirdweb-dev/react and @thirdweb-dev/sdk
  • Install packages for solana wallet adapter: @solana/wallet-adapter-react, @solana/wallet-adapter-react-ui and @solana/wallet-adapter-wallets.
  • Wrap the app in the ThirdwebProvider and WalletModalProvider.

Setting up thirdweb auth

We are going to need thirdweb auth to validate if the user has an NFT from our collection or not. We'll perform this check on the server side so that the user doesn't reach the page unless they own an NFT.

Firstly, we need to install the thirdweb auth package:

npm i @thirdweb-dev/auth # npm

yarn add @thirdweb-dev/auth # yarn

Now, create a file called auth.config.ts and the following:

import { ThirdwebAuth } from "@thirdweb-dev/auth/next";
import { PrivateKeyWallet } from "@thirdweb-dev/auth/solana";

export const { ThirdwebAuthHandler, getUser } = ThirdwebAuth({
  domain: process.env.NEXT_PUBLIC_THIRDWEB_AUTH_DOMAIN as string,
  wallet: new PrivateKeyWallet(process.env.THIRDWEB_AUTH_PRIVATE_KEY || ""),
});

Update the domain with your domain.

We need the private key of our wallet. So, go to your phantom wallet (if you haven't already created one, check out this guide) and click on the top right badge and select your wallet. Then click on the export private key button:

Export your private key from your solana wallet

Copy this private key, and paste it into a new file .env in the following format:

PRIVATE_KEY=<your-private-key>

Using private keys as an env variable is vulnerable to attacks and is not the best practice. We are doing it in this guide for the sake of brevity, but we strongly recommend using a secret manager to store your private key.

To configure the auth api, create a new folder inside pages/api called auth and [...thirdweb].ts file inside it! Here we need to export the handler that we created!

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

export default ThirdwebAuthHandler();

Finally, inside the _app.tsx file, add the authConfig prop to ThirdwebProvider:

<ThirdwebProvider
  authConfig={{
    authUrl: "/api/auth",
    domain: process.env.NEXT_PUBLIC_THIRDWEB_AUTH_DOMAIN as string,
  }}
  network={network}
>
  <WalletModalProvider>
    <Component {...pageProps} />
  </WalletModalProvider>
</ThirdwebProvider>

Building the frontend

Login Page

We will create a login page where people can do the following:

  • Connect Wallet
  • Sign in with Solana
  • Claim an NFT

First, let's create a new page inside the pages directory called login.tsx.

Within this file, let's show different UI states depending on the status of the user and publicKey:

  • If the user hasn't connected their wallet, we'll show the connect wallet button.
  • If the user hasn't signed in yet, we'll show them the sign-in button.
  • If the user has signed in, we'll show them their wallet information, as well as a Mint button so they can demo the application.
import { useWallet } from "@solana/wallet-adapter-react";
import { WalletMultiButton } from "@solana/wallet-adapter-react-ui";
import {
  useClaimNFT,
  useLogin,
  useProgram,
  useUser,
} from "@thirdweb-dev/react/solana";
import type { NextPage } from "next";
import Link from "next/link";

const Home: NextPage = () => {
  const { publicKey } = useWallet();
  const { user, isLoading: userLoading } = useUser();
  const { login } = useLogin();
  const { data: nftDrop } = useProgram("<your-program-address>", "nft-drop");
  const { mutate, isLoading } = useClaimNFT(nftDrop);

  if (userLoading) return <></>;

  return (
    <div>
      <h1>NFT Gated Website on Solana</h1>
      {!publicKey && <WalletMultiButton />}
      {publicKey && !user && <button onClick={() => login()}>Login</button>}

      {user && <p>Logged in as {user.address} </p>}

      {user && (
        <button
          onClick={() =>
            mutate({
              amount: 1,
            })
          }
        >
          {isLoading ? "Claiming..." : "Claim NFT"}
        </button>
      )}

      <Link href="/" passHref>
        Protected Page
      </Link>
    </div>
  );
};

export default Home;

You'll need to change the value of <your-program-address> to the address of your NFT Drop program.

With this, the flow of our login page is complete! Let's head over to the protected page (/) now.

Protected Page

In pages/index.tsx, I have created a very simple component like this:

import { ThirdwebSDK } from "@thirdweb-dev/sdk/solana";
import type { GetServerSideProps } from "next";
import { getUser } from "../auth.config";

const Protected = () => {
  return (
    <div>
      <h1>Protected Page</h1>
      <p>You have access to this page</p>
    </div>
  );
};

export default Protected;

Beneath this component, we will use the getServerSideProps function to first check:

  • Is the user signed in?
  • Do they own an NFT from our collection, using the SDK

If the user isn't signed in or doesn't own an NFT from our collection, we redirect them back to the login page before they can access the protected page.

export const getServerSideProps: GetServerSideProps = async ({ req }) => {
  const sdk = ThirdwebSDK.fromNetwork("devnet");

  const user = await getUser(req);

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

  const program = await sdk.getNFTDrop("<your-program-address>");
  const nfts = await program?.getAllClaimed();

  const hasNFT = nfts?.some((nft) => nft.owner === user.address);

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

  return {
    props: {},
  };
};

Again, remember to change the value of <your-program-address>.

If hasNFT is true, this means the user has signed in and owns an NFT from your NFT Drop program; at which point they'll be allowed to access the home page!

You could customize this getServersideProps function further to make a request for restricted content from a database or load any other content you want to gate behind your NFT!

Conclusion

In this guide, we built an NFT gated website on Solana using Sign In With Solana and an NFT drop.

If you have any queries hop on to the thirdweb discord! If you want to look at the code, check out the GitHub Repository.