mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-30 21:01:54 +08:00
167 lines
5.6 KiB
Plaintext
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).
|