Web3 Supabase Authentication

This tutorial will teach you how to integrate the Supabase Authentication with the Web3 authentication. We will use the Moralis Auth API to handle the Web3 authentication flow.

You will learn how to combine Web3 authentication with Supabase authentication in this guide. The Web3 authentication flow will be managed using the Moralis Auth API.

Let's start by defining Supabase. It's an open-source substitute for Firebase. Backend-as-a-Service, that is. It's a quick approach to go from Postgres schema to REST API!

In this guide, we will take advantage of Supabase row level security (RLS) as we use custom authentication provider!

600600

Let's dive in

Steps in a nutshell

  1. Setup Supabase with RLS
  2. Request message from Moralis Auth API
  3. Store user in supabase and sign JWT with user
  4. Make client-side requests using JWT.

Setup Supabase with RLS

First, let's create a Supabase account here.

We will not use the Supabase default auth schema because we do not want to use email and password authentication but rather wallet authentication, so we will create a new users table in the public schema.

On the dashboard, go to Table editor select the public schema and click Create a new table.

19091909 668668

Keep in mind that because Supadbase uses Postgres SQL, your table must be structured with all the necessary fields, in this case, id, moralis provider id and metadata.

The last thing we need to do is configure RLS policy for our table. Learn more about Supabase RLS.

We will create a policy that allows only authenticated users to access the table.

To do this go to Authentication > Policies then click Enable RLS for the table we just created > clieck on New policy for the created table.

11561156
CREATE POLICY "Enable select for authenticated users only"
ON public.users
FOR SELECT
TO authenticated
WITH CHECK (true);

Back-end

We will now develop the authentication flow's back-end logic. Firstly, install the Moralis SDK and Supabase SDK.

yarn add moralis @supabase/supabase-js jsonwebtoken

We need to initialize the Supabase SDK with the Supabase credentials.

import { createClient } from '@supabase/supabase-js';

const supabase = createClient(config.SUPABASE_URL, config.SUPABASE_SERVICE_ROLE);

the SUPABASE_URL and SUPABASE_SERVICE_ROLE can be gotten from the Supabase dashboard. Go to Settings > API and copy the values.

Note that the SUPABASE_SERVICE_ROLE is the service role that has ability to bypass Row Level Security. Never share it publicly or use in client side.

878878

The Moralis SDK must also be initialized.

import Moralis from 'moralis';

Moralis.start({
  apiKey: 'your_api_key_here',
});

Request message from Moralis Auth API

Now we can create a function that will request a message from the Moralis Auth API. This function allows us to create a message, that we will send to a user to sign it by his wallet.

export async function requestMessage({ address, chain, network }: { address: string; chain: string; network: 'evm' }) {
  const result = await Moralis.Auth.requestMessage({
    address,
    chain,
    network,
    domain: 'defi.finance',
    statement: 'Please sign this message to confirm your identity.',
    uri: 'https://defi.finance',
    expirationTime: '2023-01-01T00:00:00.000Z',
    timeout: 15,
  });

  const { message } = result.toJSON();

  return message;
}

Store user in supabase and sign JWT with user

Now we need to build a function that validates a user-signed message. The function issues a JWT token if the signed message is valid. We can send this token to the front-end application. Of course, a token can only be issued to a current user. So, we create a new account if the user is not already there.

We will need to grab SUPABASE_JWT from Settings > API because we are going to use it to sign the JWT.

Example snippet assuming the jsonwebtoken package (imported as jwt)

Learn more about Supabase JWT

export async function verifyMessage({ network, signature, message }: VerifyMessage) {
  const result = await Moralis.Auth.verify({
    network,
    signature,
    message,
  });

  const authData = result.toJSON();

  let { data: user } = await supabase.from('users').select('*').eq('moralis_provider_id', authData.profileId).single();

  if (!user) {
    const response = await supabase
      .from('users')
      .insert({ moralis_provider_id: authData.profileId, metadata: authData })
      .single();
    user = response.data;
  }

  const token = jwt.sign(
    {
      ...user,
      aud: 'authenticated',
      role: 'authenticated',
      exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7,
    },
    config.SUPABASE_JWT,
  );

  return { user, token };
}

Client-side

Now let us make the client-side that implements the authentication process.

We need to add the following dependencies using CDN:

  • Etherjs so we can sign messages with our wallet (metamask in the case of this demo)
  • Supabase SDK
  • Axios (for making HTTP requests)
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.ethers.io/lib/ethers-5.2.umd.min.js" type="application/javascript"></script>
<script src="https://unpkg.com/@supabase/supabase-js"></script>

We need to initialize the Supabase SDK with the Supabase credentials again.

const _supabase = supabase.createClient(SUPABASE_URL, SUPABASE_PUBLIC_ANON_KEY);

Notice how we used SUPABASE_PUBLIC_ANON_KEY instead of SUPABASE_SERVICE_ROLE because we don't want to bypass RLS this time.

We now want some code that retrieves the basic data about a user's wallet. The code below only works with the MetaMask provider.

const connectToMetamask = async () => {
  const provider = new ethers.providers.Web3Provider(window.ethereum, 'any');

  const [accounts, chainId] = await Promise.all([
    provider.send('eth_requestAccounts', []),
    provider.send('eth_chainId', []),
  ]);

  const signer = provider.getSigner();
  return { signer, chain: chainId, account: accounts[0] };
};

Ok, now we have everything to authenticate a user. The authentication flow is a quite simple sequence:

  • Read a basic information about a user's wallet.
  • Generate a message by the back-end function to sign it by the user.
  • Sign the message by the user's wallet.
  • Send the signed message to the back-end function and receive a token.
  • Pass the received token to the supabase.auth.setAuth(token) method.
  • Following this process, the token is used to authenticate all requests performed by Supabase..

The example code of the authentication flow is available below.

const handleAuth = async () => {
  // Connect to Metamask
  const { signer, chain, account } = await connectToMetamask();

  if (!account) {
    throw new Error('No account found');
  }
  if (!chain) {
    throw new Error('No chain found');
  }

  const { message } = await requestMessage(account, chain);

  const signature = await signer.signMessage(message);

  const { user } = await verifyMessage(message, signature);

  token = user.token;

  renderUser(user);
};

Now we can use this token to access our users table using the token returned after verification.

const getUser = async (token) => {
  _supabase.auth.setAuth(token);
  const { data } = await _supabase.from('users').select('*');
  renderUser(data);
};

Full code of the authentication flow is available on github https://github.com/MoralisWeb3/Moralis-JS-SDK/tree/main/demos/supabase-auth


Did this page help you?