mirror of
https://github.com/supabase/supabase.git
synced 2026-07-02 13:34:27 +08:00
462 lines
15 KiB
Plaintext
462 lines
15 KiB
Plaintext
---
|
||
id: auth0
|
||
title: 'Auth0'
|
||
description: 'Swap out Supabase authentication with Auth0. Let Auth0 handle tokens and signing users in and out, while Supabase enforces authorization policies with Row Level Security (RLS).'
|
||
---
|
||
|
||
This guide steps through building a Next.js application with Auth0 and Supabase. We configure Auth0 to handle authenticating users and managing tokens, while writing our authorization logic in Supabase - using Row Level Security policies.
|
||
|
||
> Note: This guide is heavily inspired by the [Using Next.js and Auth0 with Supabase](https://auth0.com/blog/using-nextjs-and-auth0-with-supabase/) article on [Auth0's blog](https://auth0.com/blog/). Check it out for a practical step-by-step guide on integrating Auth0 and Supabase.
|
||
|
||
The full code example for this guide can be found [here](https://github.com/dijonmusters/supabase-auth0-example).
|
||
|
||
[Auth0](https://auth0.com/) is an authentication and authorization platform, offering numerous strategies to authenticate and manage users. It provides fine-grain control over how users sign in to your application, the token that is generated, and what data is stored about your users.
|
||
|
||
[Next.js](https://nextjs.org/) is a web application framework built on top of React. We will be using it for this example, as it allows us to write server-side logic within our application. Auth0 have also written a [very well integrated authentication library](https://www.npmjs.com/package/@auth0/nextjs-auth0) specifically for Next.js.
|
||
|
||
> Note: API routes (serverless functions) in Next.js closely resemble the structure of Node server frameworks - such as Express, Koa and Fastify. The server-side logic in this guide could easily be refactored in one of these frameworks and managed as a separate application to the front-end.
|
||
|
||
If you don’t have an Auth0 account, create one [here](https://auth0.com/signup).
|
||
|
||
You will also need a Supabase account, which can be created by signing in [here](https://app.supabase.com/).
|
||
|
||
## Step 1: Creating an Auth0 tenant
|
||
|
||
From the Auth0 dashboard, click the menu to the right of the Auth0 logo, and select `Create tenant`.
|
||
|
||

|
||
|
||
Enter a `Domain` for your tenant - this will need to be unique.
|
||
|
||
Select a `Region` - this should be geographically close to the majority of your users.
|
||
|
||
Select `Development` for `Environment Tag` - this should be production when you're ready to go live.
|
||
|
||

|
||
|
||
## Step 2: Setting up an Auth0 application
|
||
|
||
From the sidebar menu, select `Applications` > `Applications` and click `Create Application`.
|
||
|
||
Give your application a name, select the `Regular Web Applications` option and click `Create`.
|
||
|
||

|
||
|
||
Select `Settings` and navigate to the `Application URIs` section, and update the following:
|
||
|
||
`Allowed Callback URLs`: `http://localhost:3000/api/auth/callback`
|
||
|
||
`Allowed Logout URLs`: `http://localhost:3000`
|
||
|
||
Scroll to the bottom of the `Settings` section and reveal the `Advanced Settings`.
|
||
|
||
Select `OAuth` and set `JSON Web Token Signature` to `RS256`.
|
||
|
||
Confirm `OIDC Conformant` is `Enabled`.
|
||
|
||
Click `Save` to update the settings.
|
||
|
||
## Step 3: Creating a Supabase project
|
||
|
||
From your [Supabase dashboard](https://app.supabase.com/), click `New project`.
|
||
|
||
Enter a `Name` for your Supabase project.
|
||
|
||
Enter a secure `Database Password`.
|
||
|
||
Select the same `Region` you selected for your Auth0 tenant.
|
||
|
||
Click `Create new project`.
|
||
|
||

|
||
|
||
## Step 4: Creating data in Supabase
|
||
|
||
From the sidebar menu in the [Supabase dashboard](https://app.supabase.com/), click `Table editor`, then `New table`.
|
||
|
||
Enter `todo` as the `Name` field.
|
||
|
||
Select `Enable Row Level Security (RLS)`.
|
||
|
||
Create two new columns:
|
||
|
||
- `title` as `text`
|
||
- `user_id` as `text`
|
||
- `is_complete` as `bool` with the default value `false`
|
||
|
||
Click `Save` to create the new table.
|
||
|
||

|
||
|
||
From the `Table editor` view, select the `todo` table and click `Insert row`.
|
||
|
||
Fill out the `title` field and click `Save`.
|
||
|
||

|
||
|
||
Click `Insert row` and add a couple of extra todos.
|
||
|
||

|
||
|
||
## Step 5: Building a Next.js app
|
||
|
||
Create a new Next.js project:
|
||
|
||
```bash
|
||
npx create-next-app <name-of-project>
|
||
```
|
||
|
||
Create a `.env.local` file and enter the following values:
|
||
|
||
```
|
||
AUTH0_SECRET=any-secure-value
|
||
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
|
||
NEXT_PUBLIC_SUPABASE_URL=get-from-supabase-dashboard
|
||
NEXT_PUBLIC_SUPABASE_KEY=get-from-supabase-dashboard
|
||
SUPABASE_SIGNING_SECRET=get-from-supabase-dashboard
|
||
```
|
||
|
||
> Note: Auth0 values can be found under `Settings > Basic Information` for your application.
|
||
|
||

|
||
|
||
> Note: Supabase values can be found under `Settings > API` for your project.
|
||
|
||

|
||
|
||
Restart your Next.js development server to read in the new values from `.env.local`.
|
||
|
||
```bash
|
||
npm run dev
|
||
```
|
||
|
||
## Step 6: Install Auth0 Next.js library
|
||
|
||
Install the `@auth0/nextjs-auth0` library.
|
||
|
||
```bash
|
||
npm i @auth0/nextjs-auth0
|
||
```
|
||
|
||
Create a new file `pages/api/auth/[...auth0].js` and add:
|
||
|
||
```jsx
|
||
// pages/api/auth/[...auth0].js
|
||
|
||
import { handleAuth } from '@auth0/nextjs-auth0'
|
||
|
||
export default handleAuth()
|
||
```
|
||
|
||
> Note: This will create a few API routes for us. The main ones we will use are `/api/auth/login` and `/api/auth/logout` to handle signing users in and out.
|
||
|
||
Open `pages/_app.js` and wrap our `Component` with the `UserProvider` from Auth0:
|
||
|
||
```jsx
|
||
// pages/_app.js
|
||
|
||
import React from 'react'
|
||
import { UserProvider } from '@auth0/nextjs-auth0'
|
||
|
||
const App = ({ Component, pageProps }) => {
|
||
return (
|
||
<UserProvider>
|
||
<Component {...pageProps} />
|
||
</UserProvider>
|
||
)
|
||
}
|
||
|
||
export default App
|
||
```
|
||
|
||
Update `pages/index.js` to ensure the user is logged in to view the landing page.
|
||
|
||
```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">
|
||
<a>Logout</a>
|
||
</Link>
|
||
</p>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export const getServerSideProps = withPageAuthRequired()
|
||
|
||
export default Index
|
||
```
|
||
|
||
> Note: `withPageAuthRequired` will automatically redirect the user to `/api/auth/login` if they are not currently logged in.
|
||
|
||
Test this is working by navigating to `http://localhost:3000` which should redirect you to an Auth0 sign in screen.
|
||
|
||

|
||
|
||
Either `Sign up` for a new account, or click `Continue with Google` to sign in.
|
||
|
||
You should now be able to view the landing page.
|
||
|
||

|
||
|
||
## Step 7: Sign Auth0 token for Supabase
|
||
|
||
Currently, neither Supabase or Auth0 allow for a custom signing secret to be set for their JWT. They also use different [signing algorithms](https://auth0.com/docs/configure/applications/signing-algorithms).
|
||
|
||
Therefore, we need to extract the bits we need from Auth0's JWT, and sign our own to send to Supabase.
|
||
|
||
We can do that using Auth0's `afterCallback` function, which gets called anytime the user authenticates.
|
||
|
||
Install the `jsonwebtoken` library.
|
||
|
||
```bash
|
||
npm i jsonwebtoken
|
||
```
|
||
|
||
Update `pages/api/auth/[...auth0].js` with the following:
|
||
|
||
```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)
|
||
}
|
||
},
|
||
})
|
||
```
|
||
|
||
Our `payload` for the JWT will contain our user's unique identifier from Auth0 - `session.user.sub` and an expiry of 1 hour.
|
||
|
||
We are signing this JWT using Supabase's signing secret, so Supabase will be able to validate it is authentic and hasn't been tampered with in transit.
|
||
|
||
> Note: We need to sign the user out and back in again to run the `afterCallback` function, and create our new token.
|
||
|
||
Now we just need to send the token along with the request to Supabase.
|
||
|
||
## Step 8: Requesting data from Supabase
|
||
|
||
Create a new file called `utils/supabase.js` and add the following:
|
||
|
||
```jsx
|
||
// utils/supabase.js
|
||
|
||
import { createClient } from '@supabase/supabase-js'
|
||
|
||
const getSupabase = (access_token) => {
|
||
const supabase = createClient(
|
||
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
||
process.env.NEXT_PUBLIC_SUPABASE_KEY
|
||
)
|
||
|
||
if (access_token) {
|
||
supabase.auth.session = () => ({
|
||
access_token,
|
||
})
|
||
}
|
||
|
||
return supabase
|
||
}
|
||
|
||
export { getSupabase }
|
||
```
|
||
|
||
This will be our client for talking to Supabase. We can pass it an `access_token` and it will be attached to our request.
|
||
|
||
Let's load our `todos` from Supabase in our landing page!
|
||
|
||
```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'
|
||
import { useEffect } from 'react'
|
||
|
||
const Index = ({ user }) => {
|
||
const [todos, setTodos] = useState([])
|
||
const supabase = getSupabase(user.accessToken)
|
||
|
||
useEffect(() => {
|
||
const fetchTodos = async () => {
|
||
const { data } = await supabase.from('todo').select('*')
|
||
setTodos(data)
|
||
}
|
||
|
||
fetchTodos()
|
||
}, [])
|
||
|
||
return (
|
||
<div className={styles.container}>
|
||
<p>
|
||
Welcome {user.name}!{' '}
|
||
<Link href="/api/auth/logout">
|
||
<a>Logout</a>
|
||
</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()
|
||
|
||
export default Index
|
||
```
|
||
|
||
Alternatively, we could fetch todos on the server using the `getServerSideProps` function.
|
||
|
||
```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">
|
||
<a>Logout</a>
|
||
</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 = getSupabase(accessToken)
|
||
|
||
const { data: todos } = await supabase.from('todo').select('*')
|
||
|
||
return {
|
||
props: { todos },
|
||
}
|
||
},
|
||
})
|
||
|
||
export default Index
|
||
```
|
||
|
||
Either way, when we reload our application, we are still getting the empty state for todos.
|
||
|
||

|
||
|
||
This is because we enabled Row Level Security, which blocks all requests by default. To enable our user to select their `todos` we need to write a policy.
|
||
|
||
## Step 9: Write a policy to allow select
|
||
|
||
Our policy will need to know who our currently logged in user is to determine whether or not they should have access. Let's create a PostgreSQL function to extract the current user from our new JWT.
|
||
|
||
Navigate back to the Supabase dashboard, select `SQL` from the sidebar menu, and click `New query`. This will create a new query called `new sql snippet`, which will allow us to run any SQL against our Postgres database.
|
||
|
||
Write the following 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;
|
||
```
|
||
|
||
This will create a function called `auth.user_id()`, which will inspect the `userId` field of our JWT payload.
|
||
|
||
> Note: To learn more about PostgreSQL functions, check out [our deep dive video](https://www.youtube.com/watch?v=MJZCCpCYEqk).
|
||
|
||
Let's create a policy that checks whether this user is the owner of the todo.
|
||
|
||
Select `Authentication` from the Supabase sidebar menu, click `Policies`, and then `New Policy` on the `todo` table.
|
||
|
||

|
||
|
||
From the modal, select `Create a policy from scratch` and add the following.
|
||
|
||

|
||
|
||
This policy is calling the function we just created to get the currently logged in user's ID `auth.user_id()` and checking whether this matches the `user_id` column for the current `todo`. If it does, then it will allow the user to select it, otherwise it will continue to deny.
|
||
|
||
Click `Review` and then `Save policy`.
|
||
|
||
> Note: To learn more about RLS and policies, check out [our deep dive video](https://www.youtube.com/watch?v=Ow_Uzedfohk).
|
||
|
||
The last thing we need to do is update the `user_id` columns for our existing `todos`.
|
||
|
||
Head back to the Supabase dashboard, and select `Table editor` from the sidebar.
|
||
|
||

|
||
|
||
Each of our `user_id` columns are set to `NULL`!
|
||
|
||
To get the ID for our Auth0 user, head over to the Auth0 dashboard, select `User Management` from the sidebar, click `Users` and select your test user.
|
||
|
||

|
||
|
||
Copy their `user_id`.
|
||
|
||

|
||
|
||
Update each row in Supabase.
|
||
|
||

|
||
|
||
Now when we refresh our application, we should finally see our list of `todos`!
|
||
|
||
> Note: Check out [the repo](https://github.com/dijonmusters/supabase-auth0-example/blob/main/pages/index.js) for an example of writing new `todos` to Supabase.
|
||
|
||
## Resources
|
||
|
||
- [Auth0](https://auth0.com/) official website.
|
||
- [Auth0 blog](https://auth0.com/blog/).
|
||
- [Using Next.js and Auth0 with Supabase article](https://auth0.com/blog/using-nextjs-and-auth0-with-supabase/).
|
||
- [Auth0 community](https://community.auth0.com/).
|
||
- [Auth0 documentation](https://auth0.com/docs/).
|