stack/docs-mintlify/guides/integrations/supabase/overview.mdx
2026-06-17 09:56:41 -07:00

167 lines
5.6 KiB
Plaintext

---
title: Supabase
description: Integrate Hexclave with Supabase RLS
---
This guide shows how to integrate Hexclave with Supabase row level security (RLS).
<Info>
This guide only focuses on the RLS/JWT integration and does not sync user data between Supabase and Stack. You should use [webhooks](/guides/apps/webhooks/overview) to achieve data sync.
</Info>
## Setup
Let's create a sample table and some RLS policies to demonstrate how to integrate Hexclave with Supabase RLS. You can apply the same logic to your own tables and policies.
<Steps>
<Step title="Setup Supabase">
First, let's create a Supabase project, then go to the [SQL Editor](https://supabase.com/dashboard/project/_/sql/new) and create a new table with some sample data and RLS policies.
```sql title="Supabase SQL Editor"
-- Create the 'data' table
CREATE TABLE data (
id bigint PRIMARY KEY,
text text NOT NULL,
user_id UUID
);
-- Insert sample data
INSERT INTO data (id, text, user_id) VALUES
(1, 'Everyone can see this', NULL),
(2, 'Only authenticated users can see this', NULL),
(3, 'Only user with specific id can see this', NULL);
-- Enable Row Level Security
ALTER TABLE data ENABLE ROW LEVEL SECURITY;
-- Allow everyone to read the first row
CREATE POLICY "Public read" ON "public"."data" TO public
USING (id = 1);
-- Allow authenticated users to read the second row
CREATE POLICY "Authenticated access" ON "public"."data" TO authenticated
USING (id = 2);
-- Allow only the owner of the row to read it
CREATE POLICY "User access" ON "public"."data" TO authenticated
USING (id = 3 AND auth.uid() = user_id);
```
</Step>
<Step title="Setup a new Next.js project">
Now let's create a new Next.js project and install Hexclave and Supabase client. (more details on [Next.js setup](https://nextjs.org/docs/getting-started/installation), [Hexclave setup](/guides/getting-started/setup), and [Supabase setup](https://supabase.com/docs/guides/getting-started/quickstarts/nextjs))
```bash title="Terminal"
npx create-next-app@latest -e with-supabase stack-supabase
cd stack-supabase
npx @hexclave/cli@latest init
```
Now copy the environment variables from the Supabase dashboard to the `.env.local` file:
- `NEXT_PUBLIC_SUPABASE_URL`
- `NEXT_PUBLIC_SUPABASE_ANON_KEY`
- `SUPABASE_JWT_SECRET`
Copy environment variables from the Hexclave dashboard to the `.env.local` file.
- `NEXT_PUBLIC_HEXCLAVE_PROJECT_ID`
- `NEXT_PUBLIC_HEXCLAVE_PUBLISHABLE_CLIENT_KEY`
- `HEXCLAVE_SECRET_SERVER_KEY`
</Step>
<Step title="Set up Supabase client">
Now let's create a server action that mints a supabase JWT with the Hexclave user ID if the user is authenticated.
```tsx title="/utils/actions.ts"
'use server';
import { hexclaveServerApp } from "@/hexclave/server";
import * as jose from "jose";
export const getSupabaseJwt = async () => {
const user = await hexclaveServerApp.getUser();
if (!user) {
return null;
}
const token = await new jose.SignJWT({
sub: user.id,
role: "authenticated",
})
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime('1h')
.sign(new TextEncoder().encode(process.env.SUPABASE_JWT_SECRET));
return token;
};
```
And now create a helper function to create a Supabase client with the JWT signed by the server action
```tsx title="/utils/supabase-client.ts"
import { createBrowserClient } from "@supabase/ssr";
import { getSupabaseJwt } from "./actions";
export const createSupabaseClient = () => {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{ accessToken: async () => await getSupabaseJwt() || "" }
);
}
```
</Step>
<Step title="Fetch data from Supabase">
Let's create an example page that fetches data from Supabase and displays it.
```tsx title="/app/page.tsx"
'use client';
import { createSupabaseClient } from "@/utils/supabase-client";
import { useHexclaveApp, useUser } from "@hexclave/next";
import { useEffect, useState } from "react";
export default function Page() {
const app = useHexclaveApp();
const user = useUser();
const supabase = createSupabaseClient();
const [data, setData] = useState<null | any[]>(null);
useEffect(() => {
supabase.from("data").select().then(({ data }) => setData(data ?? []));
}, []);
const listContent = data === null ?
<p>Loading...</p> :
data.length === 0 ?
<p>No notes found</p> :
data.map((note) => <li key={note.id}>{note.text}</li>);
return (
<div>
{
user ?
<>
<p>You are signed in</p>
<p>User ID: {user.id}</p>
<button onClick={async () => await app.redirectToSignOut()}>Sign Out</button>
</> :
<button onClick={async () => await app.redirectToSignIn()}>Sign In</button>
}
<h3>Supabase data</h3>
<ul>{listContent}</ul>
</div>
)
}
```
Now you should be able to compare the data you can view with an anonymous user, an authenticated user. You can also add your user Id to the row 3 of the Supabase table, and you should be able to see the row if and only if you are signed in with that user.
</Step>
</Steps>
You can find the full example [here on GitHub](https://github.com/hexclave/hexclave/tree/main/examples/supabase).