Create An NFT Gated Website
⚠️ Warning: This guide currently uses v4 of the Connect SDK. For v5 (latest) code snippets, please check out our documentation while this guide is being updated. ⚠️
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
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:
- 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.
- 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 || ""),
});
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:
- The user is authenticated
- 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.