---
title: "Using Next.js and Auth0 with Supabase"
description: "Learn how to integrate Auth0 authentication with database storage and row-level security authorization in Supabase."
authors:
  - name: "Jon Meyers"
    url: "https://auth0.com/blog/authors/jon-meyers/"
date: "May 19, 2023"
category: "Developers,Tutorial,Next.js"
tags: ["supabase", "nextjs", "auth0"]
url: "https://auth0.com/blog/using-nextjs-and-auth0-with-supabase/"
---

# Using Next.js and Auth0 with Supabase

## Overview

In this article, we are going to explore using [Next.js](https://nextjs.org/), [Auth0](https://auth0.com/), and [Supabase](https://supabase.com/) to build a classic Todo app. Each user will only be able to see their own todos, so we will need to implement authentication, authorization, and a database.

This article will cover the following:

- configuring Auth0, Next.js, and Supabase to work together seamlessly
- using the `nextjs-auth0` library for authentication
- implementing Row Level Security (RLS) policies for authorization
- what a JWT is and how to sign our own
- using PostgreSQL Functions to extract values from a JWT

The final version of the Todo app code can be found [here](https://github.com/dijonmusters/auth0-supabase-2023).

## Prerequisites

This article does not assume prior experience with any of these technologies; however, you will need [Node.js](https://nodejs.org/en/download/) installed to follow along. In addition, plan to have accounts for the managed services in the section below (Auth0 and Supabase). Both are free as of this writing and don't require a credit card.

## Stack

**Next.js** is a React framework that makes building efficient web apps super easy. It also gives us the ability to write server-side logic — which we will need to ensure our application is secure — without needing to maintain our own server.

**Auth0** is an authentication and authorization solution that makes managing users and securing applications a breeze. It is an extremely battle-tested and mature solution for auth.

**Supabase** is an open-source backend-as-a-service, which makes it possible to build an application in a weekend and scale to millions. It is a convenient wrapper around a collection of open-source tools that enable database storage, file storage, authentication, authorization, and real-time subscriptions. While these are all great features, this article will only use database storage and authorization.

"Wait, if Supabase handles auth, why are we using Auth0?"

One of the real strengths of Supabase is the lack of vendor lock-in. Maybe you already have users in Auth0, your company has a lot of experience with it, or you're interacting with other applications that use it. Any of the Supabase components can be swapped out for a similar service and hosted anywhere.

So let's do just that!

## Auth0

The first thing we need to do is sign up for a free account with [Auth0](https://auth0.com/). Once at the dashboard, we need to create a new `Tenant` for our project.

> A tenant is a way of isolating our users and settings from other applications we have with Auth0.

Click the name of your account in the top left, and then select `Create tenant` from the dropdown.

![Create tenant from Auth0 dashboard](https://images.ctfassets.net/23aumh6u8s0i/Fn7cr4qJbeL1D30qQLvTw/578b65981af2f90d8c150b337793fa12/01_Create_tenant_from_Auth0_dashboard.png)

Give your tenant a unique `Domain`, set the `Region` closest to you, and leave the `Environment Tag` set to `Development`.

![Auth0 tenant settings](https://images.ctfassets.net/23aumh6u8s0i/8xOwNQYF3xw2C4sdjTB98/3d0e2a6ddbaddcbfb69edacdbe63d66d/02_Auth0_tenant_settings.png)

> In a production application, you want your region to be as close as possible to the majority of your users.

Next, we want to create an Application. Select `Applications` > `Applications` from the sidebar menu, and click `+ Create Application`. We want to give it a name (this can be the same as the Tenant) and select `Regular Web Applications`. Click `Create`.

![Auth0 application settings](https://images.ctfassets.net/23aumh6u8s0i/2Wp6B942N5kEoh7SRoN5aH/661b969e8c4403097cb10590deba8cff/03_Auth0_application_settings.png)

From the application's page you are redirected to, select the `Settings` tab and scroll down to the `Application URIs` section.

Add the following:

`Allowed Callback URLs`: `http://localhost:3000/api/auth/callback`

`Allowed Logout URLs`: `http://localhost:3000`

Go to `Advanced Settings` > `OAuth` and confirm the `JSON Web Token (JWT) Signature Algorithm` is set to `RS256` and that `OIDC Conformant` is `enabled`. Be sure to save your changes.

Awesome. We now have an Auth0 instance configured to handle authentication for our application. So let's build an app!

While we could use any web application framework for this example, I am going to use Next.js. It gives us a super-efficient React application and includes file-based routing out of the box. In addition, it allows us to run server-side logic while building our app with `getStaticProps` and (when the user requests a page) with the `getServerSideProps` function. We will need to do things like authentication server-side, but we don't want the hassle of setting up, maintaining, and paying for another server.

## Next.js

The fastest way to create a Next.js application is by using the `create-next-app` package:

```bash
npx create-next-app supabase-auth0
```

Replace the contents of `pages/index.js` with:

```jsx
// pages/index.js

import styles from "../styles/Home.module.css";

const Index = () => {
  return <div className={styles.container}>Working!</div>;
};

export default Index;
```

Run the project in Development mode:

```bash
npm run dev
```

And confirm it is working at `http://localhost:3000`.

## Authentication

Let's integrate the `nextjs-auth0` package. This is a convenient wrapper around the Auth0 JS SDK but specifically built for Next.js:

```bash
npm i @auth0/nextjs-auth0
```

Create a new folder at `pages/api/auth/` and add a file called `[...auth0].js` with the following content:

```jsx
// pages/api/auth/[...auth0].js

import { handleAuth } from "@auth0/nextjs-auth0";

export default handleAuth();
```

> The `[...auth0].js` is a catch all route. This means that any url that starts with `/api/auth0` will load this component — `/api/auth0`, `/api/auth0/login`, `/api/auth0/some/deeply/nested/url` etc.

This is one of those awesome things `nextjs-auth0` gives us for free! Calling `handleAuth()` automatically creates a collection of convenient routes — such as `/login` and `/logout` — and all the necessary logic for handling tokens and sessions. There're no extra steps required besides calling this method.

Replace the contents of `pages/_app.js` with:

```jsx
// pages/_app.js

import "@/styles/globals.css";
import { UserProvider } from "@auth0/nextjs-auth0/client";

export default function App({ Component, pageProps }) {
  return (
    <UserProvider>
      <Component {...pageProps} />
    </UserProvider>
  );
}
```

Create a `.env.local` file in your root project folder and add the following:

```
AUTH0_SECRET=generate-this-below
AUTH0_BASE_URL=http://localhost:3000
AUTH0_ISSUER_BASE_URL=https://<name-of-your-tenant>.<region-you-selected>.auth0.com
AUTH0_CLIENT_ID=get-from-auth0-dashboard
AUTH0_CLIENT_SECRET=get-from-auth0-dashboard
```

> See [the Next.js documentation for Environment variables](https://nextjs.org/docs/basic-features/environment-variables) to learn more.

Generate a secure `AUTH0_SECRET` by running:

```bash
node -e "console.log(crypto.randomBytes(32).toString('hex'))"
```

> `AUTH0_CLIENT_ID` and `AUTH0_CLIENT_SECRET` can be found at `Applications > Settings > Basic Information` in the Auth0 Dashboard.

> You will need to quit the Next.js server and re-run the `npm run dev` command anytime new environment variables are added to the `.env.local` file

Let's update our `pages/index.js` file to add the ability to sign in and out:

```jsx
// pages/index.js

import styles from "../styles/Home.module.css";
import { useUser } from "@auth0/nextjs-auth0/client";
import Link from "next/link";

const Index = () => {
  const { user } = useUser();

  return (
    <div className={styles.container}>
      {user ? (
        <p>
          Welcome {user.name}! <Link href="/api/auth/logout">Logout</Link>
        </p>
      ) : (
        <Link href="/api/auth/login">Login</Link>
      )}
    </div>
  );
};

export default Index;
```

We are using the `useUser()` hook to get the `user` object if they have signed in. If not, we are rendering a link to the Login page.

> Next.js' `Link` component is being used to enable client-side routing rather than needing to reload the entire page from the server.

We can also add the ability to handle the `loading` and `error` states:

```jsx
// pages/index.js

import styles from "../styles/Home.module.css";
import { useUser } from "@auth0/nextjs-auth0/client";
import Link from "next/link";

const Index = () => {
  const { user, error, isLoading } = useUser();

  if (isLoading) return <div className={styles.container}>Loading...</div>;
  if (error) return <div className={styles.container}>{error.message}</div>;

  // rest of component
};

export default Index;
```

We need our users to be signed in to see their `todos` or add a new `todo`. To accomplish this, we'll protect this route — requiring the user to be signed in. We'll also automatically redirect them to the `/login` route if they are not.

Thankfully, the `nextjs-auth0` library makes this super simple with the `withPageAuthRequired` function. We can tell Next.js to call this function on the server before rendering our page by setting it to the `getServerSideProps` function.

Add the following to the `pages/index.js` file:

```jsx
// other imports
import { withPageAuthRequired } from "@auth0/nextjs-auth0";

// rest of component

export const getServerSideProps = withPageAuthRequired();

// other export
```

This function checks if we have a user signed in and handles redirecting them to the Login page if not. If we have a user, it automatically passes the `user` object to our `Index` component as a prop. Since this is happening on the server before our component is rendered, we no longer need to handle loading, error states, or whether or not the user is logged in. This means we can significantly clean up our rendering logic.

This is what our entire file should look like:

```jsx
// pages/index.js

import styles from "../styles/Home.module.css";
import { withPageAuthRequired } from "@auth0/nextjs-auth0";
import Link from "next/link";

const Index = ({ user }) => {
  return (
    <div className={styles.container}>
      <p>
        Welcome {user.name}! <Link href="/api/auth/logout">Logout</Link>
      </p>
    </div>
  );
};

export const getServerSideProps = withPageAuthRequired();

export default Index;
```

Very clean!

We want to display a list of todos as the landing page, but first, we need somewhere to store them.

## Supabase

Head over to [database.new](http://database.new) to create a free Supabase account. From the dashboard, click `New project` and choose your `Organization`.

Enter a name and password, and select a region geographically close to what you chose for your Auth0 region.

![New Supabase project settings](https://images.ctfassets.net/23aumh6u8s0i/7iwOogOS7u2jxzXoxjpJVi/007a3db4a38378a442f0c52b0610d650/04_New_Supabase_project_settings.png)

> Make sure you choose a secure password, as this will be the password for your PostgreSQL Database.

It will take a few minutes for Supabase to provision all the bits in the background, but this page conveniently displays all the values we need to get our Next.js app configured.

You can also get them anytime from [your project's API settings](https://app.supabase.com/project/_/settings/api).

![Supabase app URL and secrets](https://images.ctfassets.net/23aumh6u8s0i/7GnOnDB3JW6h8b3Mp0Ofck/faf11f0f3845b4613f3fd8b94e86ca34/05_Supabase_app_URL_and_secrets.png)

Add these values to the `.env.local` file:

```
NEXT_PUBLIC_SUPABASE_URL=your-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-public-key
SUPABASE_SIGNING_SECRET=your-jwt-secret
```

> Prepending an environment variable with `NEXT_PUBLIC_` makes it available in the Next.js client. All other values will only be available in `getStaticProps`, `getServerSideProps`, and serverless functions in the `pages/api/` directory.

> Adding new values to the `.env.local` file requires a restart of the Next.js dev server.

Hopefully, that stalled you long enough for provisioning to complete, and your Supabase project is ready to go.

Click the `Table editor` icon in the sidebar menu and select `+ Create a new table`.

![Create new table button](https://images.ctfassets.net/23aumh6u8s0i/6BWbphvjI7HQDmuT0IaS3k/8c38009e53e3403c47fe11c1d888de8d/06_Create_new_table_button.png)

Create a `todos` table and add columns for `content`, `user_id`, and `is_complete`.

![New table settings](https://images.ctfassets.net/23aumh6u8s0i/3goWHgmCaQdc8ZYJSLiduc/8bbb9df20f153f061b6329981b0fa33c/07_New_table_settings.png)

- `content` will be the text displayed for our todo.
- `user_id` will be the user that owns the todo.
- `is_complete` will signify whether the todo is done yet. We are setting the default value to `false`, as we would assume for a new todo.

> Leave `Row Level Security` disabled for now. We will worry about this later.

Click `Insert row` to create some example `todos`.

![New row settings](https://images.ctfassets.net/23aumh6u8s0i/6VQnWPjCPYkEw5FRpwXNcJ/9caa5b8731ad2353cf99f4d999e96868/08_New_row_settings.png)

> We can leave `user_id` blank and the default value of `false` for `is_complete`.

Let's head back to our Next.js application and install the `supabase-js` library:

```bash
npm i @supabase/supabase-js
```

Create a new folder called `utils` and add a file called `supabase.js`:

```jsx
// utils/supabase.js

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

const getSupabase = () => {
  const supabase = createClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
  );

  return supabase;
};

export { getSupabase };
```

> We are making this a function as we will need to extend it later.

This function uses the environment variables we declared earlier to create a new Supabase client. Let's use our new client to fetch `todos` in `pages/index.js`.

> We can pass a configuration object to the `withPageAuthRequired` function and declare our own `getServerSideProps` function, which will only run if the user is signed in.

```jsx
// pages/index.js

export const getServerSideProps = withPageAuthRequired({
  async getServerSideProps() {
    const supabase = getSupabase();

    const { data: todos } = await supabase.from("todos").select();

    return {
      props: { todos },
    };
  },
});
```

We need to remember to import the `getSupabase` function:

```jsx
import { getSupabase } from "../utils/supabase";
```

And now, we can iterate over our `todos` and display them in our component:

```jsx
const Index = ({ user, todos }) => {
  return (
    <div className={styles.container}>
      <p>
        Welcome {user.name}! <Link href="/api/auth/logout">Logout</Link>
      </p>
      {todos.map((todo) => (
        <p key={todo.id}>{todo.content}</p>
      ))}
    </div>
  );
};
```

We can also handle the case where there are no `todos` to display:

```jsx
const Index = ({ user, todos }) => {
  return (
    <div className={styles.container}>
      <p>
        Welcome {user.name}! <Link href="/api/auth/logout">Logout</Link>
      </p>
      {todos?.length > 0 ? (
        todos.map((todo) => <p key={todo.id}>{todo.content}</p>)
      ) : (
        <p>You have completed all todos!</p>
      )}
    </div>
  );
};
```

> The `todos?.length` statement is using [Optional Chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining). This is a fallback in case the `todos` prop is `undefined` or `null`.

Our whole component should look something like this:

```jsx
// pages/index.js

import styles from "../styles/Home.module.css";
import { withPageAuthRequired } from "@auth0/nextjs-auth0";
import { getSupabase } from "../utils/supabase";
import Link from "next/link";

const Index = ({ user, todos }) => {
  return (
    <div className={styles.container}>
      <p>
        Welcome {user.name}! <Link href="/api/auth/logout">Logout</Link>
      </p>
      {todos?.length > 0 ? (
        todos.map((todo) => <p key={todo.id}>{todo.content}</p>)
      ) : (
        <p>You have completed all todos!</p>
      )}
    </div>
  );
};

export const getServerSideProps = withPageAuthRequired({
  async getServerSideProps() {
    const supabase = getSupabase();

    const { data: todos } = await supabase.from("todos").select();

    return {
      props: { todos },
    };
  },
});

export default Index;
```

Awesome! We should now be seeing our todos in our Next.js app.

![List of todo items](https://images.ctfassets.net/23aumh6u8s0i/2RFnLtQ7zG2ZLhog7slyOw/74469ac0a3296e20996c3f1801b404d1/09_List_of_todo_items.png)

But wait, we see _all_ the todos.

We only want users to see _their_ todos. For this, we need to implement authorization. Let's create a helper function in Postgres to extract the currently logged-in user from the request's JWT.

## PostgreSQL Functions

Head back to the Supabase dashboard, click `SQL Editor` in the side panel, and select `New query`. This will create a snippet entitled `Untitled query`. Add the following SQL blob and click `RUN`.

```sql
create or replace function auth.user_id() returns text as $$
  select nullif(current_setting('request.jwt.claims', true)::json->>'userId', '')::text;
$$ language sql stable;
```

Okay, this one may look a little intimidating. Let's break down the bits we need to understand:

1. We are creating a new function called `user_id`.
2. The `auth.` part is just a way to namespace it as it is related to auth — also called a schema and is a convention in Postgres.
3. This function will return a `text` value.
4. The body of the function is fetching the value from the `userId` field of the `jwt` that came along with the `request`. We will talk about JWTs later in the article.
5. If there is no `request.jwt.claim.userId`, we are just returning an empty string — `''`.

Not that scary!

> Check out [this video on Postgres functions](https://www.youtube.com/watch?v=MJZCCpCYEqk) to learn more.

Let's enable `Row Level Security` and use our new function to ensure only users who own the `todo` can see it.

## Row Level Security

Because Supabase is just a PostgreSQL Database under the hood, we can take advantage of a killer feature — Row Level Security (RLS). RLS allows us to write authorization rules in the database itself, which can be much more efficient and much more secure!

Head back to the Supabase dashboard, and from the side panel, select `Authentication` > `Policies` and click `Enable RLS` for the `todos` table.

![Supabase dashboard showing RLS enabled for todos table](https://images.ctfassets.net/23aumh6u8s0i/4ht4pk0T4ggeNJEzbfBBg7/c4945b06e59630058194634cc47bf83c/10_Supabase_dashboard_showing_RLS_enabled_for_todos_table.png)

Now, if we refresh our application, we will see the empty state message.

![Empty todo list](https://images.ctfassets.net/23aumh6u8s0i/6U1pBpaCgRax6tkOelql1V/697548f404b603ffb9c6dea33299cc4e/11_Empty_todo_list.png)

Where did our `todos` go?

By default, RLS will deny access to all rows. If we want a user to see their `todos` we need to write a policy.

Back in the Supabase dashboard, click `New Policy`, then `Create a policy from scratch` and fill in the following:

![Policy settings for SELECT](https://images.ctfassets.net/23aumh6u8s0i/1CsIGgr7d7UTOAj8oaXJoH/c55213f96471436fb64d46400a3087ef/12_User_ID_null_in_Supabase_Table_Editor.png)

This might look a little unfamiliar, so let's break it down.

1. We are giving our policy a `name`. This can be anything.
2. We declare which actions we would like to enable. Since we want users to be able to read their own `todos`, we are choosing `SELECT`.
3. Target roles allow us to scope down the policy even further, but we can just leave it as the default of `public`.
4. We need to specify a condition that can be `true` or `false`. If it evaluates to `true` the action will be allowed. Otherwise, it will continue to be denied.
5. We call our `auth.user_id()` function to get the currently logged-in user's `id` and compare it to the `user_id` column for this `todo`.

> I recommend checking out [this video](https://www.youtube.com/watch?v=Ow_Uzedfohk) to learn more about how awesome and powerful Row Level Security is in Supabase.

Click `Review` to see the SQL that is being generated for us.

![Generated SQL for SELECT policy](https://images.ctfassets.net/23aumh6u8s0i/1v1LkrSMVNPudTADsAX1NW/8b7b4690989c9fed1aeb5bd9328c0f51/13_List_of_users_in_Auth0_dashboard.png)

Not even scary. It has just formatted the fields we entered into the correct SQL syntax.

While we're here, let's add a policy for creating new `todos`. This will be an `INSERT` action:

![Policy settings for INSERT](https://images.ctfassets.net/23aumh6u8s0i/7t2jhHwvUIHwo5E8fVNclW/1505e47a9b0a18466796b2a2b3354b18/14_User_ID_in_Auth0_dashboard.png)

> While we probably want to permit users to perform all CRUD actions, it is good practice to specify separate policies rather than selecting `ALL`. This makes them easier to extend and remove in the future.

Our application does not yet need to be able to update or delete `todos`; therefore, we will not create policies for these actions.

> It is good security practice to enable the minimum amount of permissions for the application to function. We can easily write policies for these actions in the future if we want to enable them.

Let's see if we can view our todos yet by refreshing our Next.js app.

![Empty todo list](https://images.ctfassets.net/23aumh6u8s0i/6U1pBpaCgRax6tkOelql1V/697548f404b603ffb9c6dea33299cc4e/11_Empty_todo_list.png)

Still no todos 🙁 Did we mess something up?

No! We just need to add a little bit more glue to transform the JWT that Auth0 is giving our Next.js application to the format that Supabase is expecting.

## What is a JWT?

To understand this problem, we must first understand what a JWT is. A JWT encodes a JSON object into a big string that we can use to send data between different services.

By default, the data in a JWT is not encrypted or private, just encoded.

> Aditya Shukla wrote [a great article](https://medium.com/swlh/the-difference-between-encoding-encryption-and-hashing-878c606a7aff) about the differences between encoding, encryption, and hashing. Give it a read to learn more.

![Screenshot from JWT.io showing the encoded and decoded values of a JWT](https://images.ctfassets.net/23aumh6u8s0i/72MlE1s4d8XQIZCdYQm54L/0ab71e3399aa9781913c9708bdf13dc2/15_User_ID_set_to_Auth0_user.png)

This is an example taken from [jwt.io](http://jwt.io) — a great tool for working with JWTs. On the left, we can see the JWT value. On the right, we can see what each part represents when decoded. We have some header information about how the JWT was encoded, the payload of user data, and a signature that can be used to `verify` the token.

Don't put secret things in a JWT!!

The reason we can trust JWTs for authentication is that we can `sign` it using a secret value. This value is run through an algorithm with the payload data, and a JWT string comes out the other side. We can use the signing secret to `verify` the JWT on our server. This ensures that an attacker hasn't tinkered with our token in transit. If they have, the value of the JWT will be different, and it will fail verification.

The only way it could be modified and pass the `verify` step is if someone has your signing secret. This would be bad. And this is why we can only sign JWTs with our server — or in the case of Next.js, the `getStaticProps`, `getServerSideProps` or API routes in the `pages/api` directory.

Never expose the signing secret to the client!!

![jwt secret](https://images.ctfassets.net/23aumh6u8s0i/1bSgltFxpVMtEEzKGYp1Lv/68c7a7ea615447951a3d7e95d4e1aa94/jwt_secret.jpg)

Okay, so now that we understand JWTs, what is the problem?

The signing secret used by Auth0 does not match Supabase's signing secret. While we're not using Supabase for authentication, it still uses the secret to verify the JWT each time we request data. Neither of these services makes the signing secret's value configurable.

To solve this problem, we can just grab the `sub` property — the user's ID — from Auth0 and `sign` a new token using the signing secret that Supabase is expecting.

## Signing a JWT

When working with JWTs, we want to use a well-trusted library from a reputable source. Auth0 has a very widely used and trusted one called `jsonwebtoken`.

Let's install it:

```bash
npm i jsonwebtoken
```

We can use another hook that the `@auth0/nextjs-auth0` library gives us to run some logic after the user signs in. This is called `afterCallback` and can be passed as a configuration to our `handleAuth` function. Let's replace the contents of the `pages/api/auth/[...auth0].js` file with:

```jsx
// pages/api/auth/[...auth0].js

import { handleAuth, handleCallback } from "@auth0/nextjs-auth0";

const afterCallback = async (req, res, session) => {
  // do some stuff
  // modify the session

  return session;
};

export default handleAuth({
  async callback(req, res) {
    try {
      await handleCallback(req, res, { afterCallback });
    } catch (error) {
      res.status(error.status || 500).end(error.message);
    }
  },
});
```

In this function, we want to:

1. get the user object from Auth0's session
2. create a new payload
3. sign a new token using Supabase's signing secret

Let's extend our `afterCallback` function to perform this logic:

```jsx
// pages/api/auth/[...auth0].js

// other imports
import jwt from "jsonwebtoken";

const afterCallback = async (req, res, session) => {
  const payload = {
    userId: session.user.sub,
    exp: Math.floor(Date.now() / 1000) + 60 * 60,
  };

  session.user.accessToken = jwt.sign(
    payload,
    process.env.SUPABASE_SIGNING_SECRET
  );

  return session;
};
```

The `sub` field represents Auth0's unique ID for this user. Since this is the only value we actually need to tell Supabase who our user is, we can create a new payload that contains only this value.

> It is good security practice to only give things the minimum amount of data and permissions they need to handle the task.

Additionally, we are setting an expiry for our token — 1 hour. This means that if someone does get a hold of our token, they do not have access to our database indefinitely.

Lastly, we are signing a new token with that payload using the `SUPABASE_SIGNING_SECRET`.

Awesome! Our whole file should look something like this:

```jsx
// pages/api/auth/[...auth0].js

import { handleAuth, handleCallback } from "@auth0/nextjs-auth0";
import jwt from "jsonwebtoken";

const afterCallback = async (req, res, session) => {
  const payload = {
    userId: session.user.sub,
    exp: Math.floor(Date.now() / 1000) + 60 * 60,
  };

  session.user.accessToken = jwt.sign(
    payload,
    process.env.SUPABASE_SIGNING_SECRET
  );

  return session;
};

export default handleAuth({
  async callback(req, res) {
    try {
      await handleCallback(req, res, { afterCallback });
    } catch (error) {
      res.status(error.status || 500).end(error.message);
    }
  },
});
```

Let's extend our `getSupabase` function in `utils/supabase.js` to accept an `accessToken` parameter.

```jsx
// utils/supabase.js

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

const getSupabase = async (accessToken) => {
  const supabase = createClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
    {
      global: {
        headers: {
          Authorization: `Bearer ${accessToken}`,
        },
      },
    }
  );

  return supabase;
};

export { getSupabase };
```

Now we can attach an `accessToken` from an external source - like Auth0 - and this will be sent along with all requests to Supabase, making it available to our `select` and `insert` RLS policies.

Let's extend our `getServerSideProps` function in `pages/index.js` to get the `accessToken` from the session's user and pass it to the `getSupabase` function:

```jsx
// pages/index.js

export const getServerSideProps = withPageAuthRequired({
  async getServerSideProps({ req, res }) {
    const {
      user: { accessToken },
    } = await getSession(req, res);

    const supabase = getSupabase(accessToken);

    const { data: todos } = await supabase.from("todos").select();

    return {
      props: { todos },
    };
  },
});
```

We also need to remember to add `getSession` to the import from `@auth0/nextjs-auth0`:

```jsx
import { withPageAuthRequired, getSession } from "@auth0/nextjs-auth0";
```

Our whole component should look something like this:

```jsx
// pages/index.js

import styles from "../styles/Home.module.css";
import { withPageAuthRequired, getSession } from "@auth0/nextjs-auth0";
import { getSupabase } from "../utils/supabase";
import Link from "next/link";

const Index = ({ user, todos }) => {
  return (
    <div className={styles.container}>
      <p>
        Welcome {user.name}! <Link href="/api/auth/logout">Logout</Link>
      </p>
      {todos?.length > 0 ? (
        todos.map((todo) => <p key={todo.id}>{todo.content}</p>)
      ) : (
        <p>You have completed all todos!</p>
      )}
    </div>
  );
};

export const getServerSideProps = withPageAuthRequired({
  async getServerSideProps({ req, res }) {
    const {
      user: { accessToken },
    } = await getSession(req, res);

    const supabase = await getSupabase(accessToken);

    const { data: todos } = await supabase.from("todos").select();

    return {
      props: { todos },
    };
  },
});

export default Index;
```

Since Auth0's `afterCallback` function runs after the user signs in, we need to log out of our application by clicking the `Logout` link on the landing page or manually navigating to `http://localhost:3000/api/auth/logout`.

This will sign us out and automatically redirect us to Auth0's sign in page.

Sign back in, and we should have our new JWT attached to our Auth0 session's user.

Now when we refresh our application, we should finally see todos.

![Empty todo list](https://images.ctfassets.net/23aumh6u8s0i/6U1pBpaCgRax6tkOelql1V/697548f404b603ffb9c6dea33299cc4e/11_Empty_todo_list.png)

Nope!

But we are very close. If we look at the `Table Editor` in the Supabase dashboard, who is the user that owns the todos?

![User ID null in Supabase Table Editor](https://images.ctfassets.net/23aumh6u8s0i/75yb3Wx93VoKO8IRwQ3FWQ/836829c8eac62b27444b4d0ee22b16e0/16_User_ID_null_in_Supabase_Table_Editor.png)

NULL!!

So we just need to find out what our current `user_id` is and add it to those rows.

Head back to the Auth0 dashboard, click `User Management` > `Users` in the sidebar, and select your user.

![List of users in Auth0 dashboard](https://images.ctfassets.net/23aumh6u8s0i/3g2UhOdH3cDsNsfq5aeMF4/60eac3fda79d3ab5af6000146b89c510/17_List_of_users_in_Auth0_dashboard.png)

The `user_id` is displayed at the top of your user's details page.

![User ID in Auth0 dashboard](https://images.ctfassets.net/23aumh6u8s0i/1utc4McUVXg7CSD0Rwyd0I/c633e72bd3363631c6dcf71837b8d7ec/18_User_ID_in_Auth0_dashboard.png)

Let's copy this value and paste it as the `user_id` for the `todos`.

![User ID set to Auth0 user](https://images.ctfassets.net/23aumh6u8s0i/5XZ168T21GhSIWcMmSIR6/7ebaa946ac6deec5e3a0bcb3e1a9cb2e/19_User_ID_set_to_Auth0_user.png)

Refresh our Next.js application.

Voilà! Todos!!!

![List of todo items](https://images.ctfassets.net/23aumh6u8s0i/2RFnLtQ7zG2ZLhog7slyOw/74469ac0a3296e20996c3f1801b404d1/09_List_of_todo_items.png)

The last thing we need to implement is the functionality to add a `todo`.

Let's add the form logic to our `pages/index.js` component.

```jsx
// pages/index.js

// other imports
const Index = ({ user, todos }) => {
  const [content, setContent] = useState("");
  const [allTodos, setAllTodos] = useState([...todos]);

  const handleSubmit = async (e) => {
    e.preventDefault();
    const supabase = await getSupabase(user.accessToken);

    const { data } = await supabase
      .from("todos")
      .insert({ content, user_id: user.sub })
      .select();

    setAllTodos([...todos, data[0]]);
    setContent("");
  };

  return (
    <div className={styles.container}>
      <p>
        Welcome {user.name}! <Link href="/api/auth/logout">Logout</Link>
      </p>
      <form onSubmit={handleSubmit}>
        <input onChange={(e) => setContent(e.target.value)} value={content} />
        <button>Add</button>
      </form>
      {allTodos?.length > 0 ? (
        allTodos.map((todo) => <p key={todo.id}>{todo.content}</p>)
      ) : (
        <p>You have completed all todos!</p>
      )}
    </div>
  );
};

// exports
```

Our final component should look something like this:

```jsx
// pages/index.js

import styles from "../styles/Home.module.css";
import { withPageAuthRequired, getSession } from "@auth0/nextjs-auth0";
import { getSupabase } from "../utils/supabase";
import Link from "next/link";
import { useState } from "react";

const Index = ({ user, todos }) => {
  const [content, setContent] = useState("");
  const [allTodos, setAllTodos] = useState([...todos]);

  const handleSubmit = async (e) => {
    e.preventDefault();
    const supabase = await getSupabase(user.accessToken);

    const { data } = await supabase
      .from("todos")
      .insert({ content, user_id: user.sub })
      .select();

    setAllTodos([...todos, data[0]]);
    setContent("");
  };

  return (
    <div className={styles.container}>
      <p>
        Welcome {user.name}! <Link href="/api/auth/logout">Logout</Link>
      </p>
      <form onSubmit={handleSubmit}>
        <input onChange={(e) => setContent(e.target.value)} value={content} />
        <button>Add</button>
      </form>
      {allTodos?.length > 0 ? (
        allTodos.map((todo) => <p key={todo.id}>{todo.content}</p>)
      ) : (
        <p>You have completed all todos!</p>
      )}
    </div>
  );
};

export const getServerSideProps = withPageAuthRequired({
  async getServerSideProps({ req, res }) {
    const {
      user: { accessToken },
    } = await getSession(req, res);

    const supabase = await getSupabase(accessToken);

    const { data: todos } = await supabase.from("todos").select();

    return {
      props: { todos },
    };
  },
});

export default Index;
```

We now have an input field above our list of `todos`. When we enter the `content` for our todo and click `add`, a new `todo` will be inserted into our DB. We are also setting the `user_id` column to the value of our `user.sub`, so we know who the `todo` belongs to.

> We're introducing `allTodos` so that we can call `setAllTodos` after the insertion of a new `todo`. It will trigger a partial re-render, displaying the newly inserted `todo` without a full page refresh.

Awesome! We now have a Next.js application using Auth0 for all things authentication and Supabase with RLS policies for authorization. We learned about JWTs and how to sign our own, as well as writing a Postgres function to look up values in a JWT in our database.

If you liked this article, [follow me on Twitter](https://twitter.com/jonmeyers_io), [check out my blog](https://jonmeyers.io/blog), and [subscribe to my newsletter](https://jonmeyers.io/subscribe/newsletter) for exclusive content and early access to courses.

For all things Supabase [follow our Twitter](https://twitter.com/supabase), [subscribe to the YouTube channel](https://www.youtube.com/supabase), and [check out our blog](https://supabase.com/blog).

Thanks for reading!