Docs

Supabase

This page will show you how to integrate Auth with the Supabase BaaS solution to let your users securely link their wallets to their Supabase accounts.

Supabase doesn't yet support custom authentication methods, so you can't yet have users login with just their wallets - but you can link wallets to preexisting Supabase auth accounts, allowing you to interact with your users onchain.

If you want to interact with the code for the Auth + Supabase integration that we'll be building in this guide, you can checkout the following GitHub repository, or clone it with the command below:

npx thirdweb create app --template thirdweb-auth-supabase
thirdweb-auth-supabase

A working example of Auth + Supabase

Prerequisites

Before starting with integration, you'll need to create a new Supabase project. You can do this by heading over to the Supabase Dashboard, creating an account if you don't have one, and then clicking the button to create a new project.

Next, we'll need to set up an authentication provider on Supabase for our users to login. These will be the accounts that we link our user's wallet addresses to. In this guide, we're going to use the Google Authentication provider, which you can learn to set with the Supabase Google Auth documentation - but you're free to use any of the other authentication providers supported by Supabase.

Note that to set up Google Authentication, you'll need to create a new project with OAuth enabled on the Google Cloud Console, and then add the Google OAuth credentials to your Supabase project.

Setup

Once you've created your Supabase app and set up Google authentication, you'll be ready to start integrating Auth into your application.

To begin with, let's create a new Next.js project with thirdweb configured:

npx thirdweb create --app --next --ts

From within the created project, we'll need to install the @thirdweb-dev/auth and @supabase/supabase-js packages:

npm install @thirdweb-dev/auth @supabase/supabase-js

Configure Supabase

Next, you'll need to configure your application to communicate with the Supabase project you set up.

To do this, create a .env.local file in the root of your project, and add the following environment variables, which you can find on the settings page of your Supabase project:

NEXT_PUBLIC_SUPABASE_URL=<supabase-url>
NEXT_PUBLIC_SUPABASE_ANON_KEY=<supabase-anon-key>
SUPABASE_SERVICE_ROLE=<supabase-service-role>

Create a new directory called lib and create two helper scripts to initialize Supabase in the browser and server:

Now we have an easy way to access Supabase Auth and Database in both client and server environments!

Configure thirdweb Auth

Finally, to configure thirdweb Auth, we just need to add the NEXT_PUBLIC_THIRDWEB_AUTH_DOMAIN environment variable to the .env.local file as follows:

NEXT_PUBLIC_THIRDWEB_AUTH_DOMAIN=<thirdweb-auth-domain>

The NEXT_PUBLIC_THIRDWEB_AUTH_DOMAIN is used to prevent phishing attacks - and is usually set to the domain of your project like example.com. You can read more about it in the thirdweb Auth Documentation.

ThirdwebProvider

Inside the pages/_app.tsx file, configure the authConfig option with your domain:

import { ThirdwebProvider } from "@thirdweb-dev/react";
export default function MyApp({ Component, pageProps }) {
return (
<ThirdwebProvider
authConfig={{
// Set this to your domain to prevent signature malleability attacks.
domain: process.env.NEXT_PUBLIC_THIRDWEB_AUTH_DOMAIN,
}}
clientId="your-client-id"
>
<Component {...pageProps} />
</ThirdwebProvider>
);
}

Sign in users

Now that we've set up the provider, we can set up the initial flow to allow users to sign in using Supabase authentication. In this case, we'll use the supabase.auth.signInWithOAuth function with the Google provider, since we configured that provider on the Supabase dashboard.

import { createSupabaseClient } from "../lib/createSupabase";
export default function Home() {
const { auth } = createSupabaseClient();
return (
<div>
<button
onClick={() =>
auth.signInWithOAuth({
provider: "google",
})
}
>
Login with Google
</button>
</div>
);
}

Getting the user data on the client side

Once users log in, we'll want a way to identify that they have actually successfully logged in. To do this, we'll use the supabase.auth.getUser and supabase.auth.getSession functions to keep track of the logged-in user whenever the user's authentication state changes.

We can bundle this functionality into a convenient hook as follows:

import { User, Session } from "@supabase/supabase-js";
import { useEffect, useState, useCallback } from "react";
// Helpful hook for you to get the currently authenticated user
export default function useSupabaseUser() {
const [user, setUser] = useState<User | null>(null);
const [session, setSession] = useState<Session | null>(null);
const { auth } = createSupabaseClient();
// Make a function to fetch the user and session
const refreshUser = useCallback(async () => {
const {
data: { user },
} = await auth.getUser();
setUser(user || null);
const {
data: { session },
} = await auth.getSession();
setSession(session ?? null);
}, [auth]);
// Add a listener to refetch the user when the session changes
useEffect(() => {
const {
data: { subscription },
} = auth.onAuthStateChange(async (event, session) => {
refreshUser();
});
refreshUser();
}, [auth]);
return { user, session, refresh: refreshUser };
}

After the user's login, we want to create a way for them to associate their verified wallet address to their Supabase account. We can do this by creating a new API endpoint that verifies the client-side user's wallet address, and then updates their account on Supabase.

We can accomplish this with the following code:

import { NextApiRequest, NextApiResponse } from "next";
import { verifyLogin } from "@thirdweb-dev/auth/evm";
import { createSupabaseServer } from "../../lib/createSupabaseAdmin";
export default async (req: NextApiRequest, res: NextApiResponse) => {
const { payload, access_token } = req.body;
// Use the Supabase service role to access the database with full permissions
const supabase = createSupabaseServer();
// Get the user from our database using the client-side access token
const {
data: { user },
} = await supabase.auth.getUser(access_token);
// Verify that the signed login payload is valid
const { address, error: verifyErr } = await verifyLogin(
process.env.NEXT_PUBLIC_THIRDWEB_AUTH_DOMAIN as string,
payload,
);
if (!address) {
return res.status(400).json({ error: verifyErr });
}
// Update the user's address in our database
await supabase.auth.admin.updateUserById(user.id, {
user_metadata: { address: address.toLowerCase() },
});
return res.status(200).end();
};

Here, we use the verifyLogin function to ensure that the user has sent a valid, signed login payload to the backend, which we can use to securely extract the wallet address of the client-side user. Once we do this, we can update the user database entry by getting the user id from the client-side access_token, and then using the supabase.auth.admin.updateUserById function to update the user's metadata with their wallet address.

Linking wallet addresses to accounts

Finally, with everything set up, we're ready to actually implement the client-side flow of prompting the user to sign a sign-in with a wallet message, sending it to the backend, and then linking the user's wallet address to their Supabase account.

Here, we use the useAuth hook to call the thirdwebAuth.login function and prompt the user to sign a login message. Then, we post this payload, along with the user's current session access token to the /api/link endpoint we created to finish linking the user's wallet address.

import {
useAddress,
useMetaMask,
useAuth,
} from "@thirdweb-dev/react";
export default function Home() {
const address = useAddress();
const connect = useMetaMask();
const thirdwebAuth = useAuth();
const { auth } = createSupabaseClient();
const { user, session, refresh } = useSupabaseUser();
// Link verified wallet address to our Supabase account
async function linkWallet() {
const payload = await thirdwebAuth?.login();
await fetch("/api/link", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
payload,
access_token: session?.access_token,
}),
});
refresh();
}
return (
<div>
{!user ? (
<button
onClick={() =>
auth.signInWithOAuth({
provider: "google",
})
}
>
Login with Google
</button>
) : !address ? (
<button onClick={connect}>Connect Wallet</button>
) : (
<button onClick={linkWallet}>Connect Wallet</button>
)}
<p>User {user.user_metadata.address}</p>
</div>
);
}

Importantly, we now have access to the user's address via the user.user_metadata.address field, which we can use to make queries or policies in our database!

So with these steps, we've now set up our Supabase to allow users to sign in with Supabase authentication and then link their wallets to their accounts!

You can now unlock all the functionality of Supabase Auth and Supabase using wallets!