mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
* docs: indicate publishable key instead of anon in many examples * replace your-anon-key to string indicating publishable or anon * fix your_... * apply suggestion from @ChrisChinchilla Co-authored-by: Chris Chinchilla <chris@chrischinchilla.com> * Update keys in code examples * Prettier fix * Update apps/docs/content/guides/functions/schedule-functions.mdx --------- Co-authored-by: Chris Chinchilla <chris@chrischinchilla.com>
958 lines
23 KiB
Plaintext
958 lines
23 KiB
Plaintext
---
|
|
title: 'Creating a Supabase client for SSR'
|
|
subtitle: 'Configure your Supabase client to use cookies'
|
|
---
|
|
|
|
To use Server-Side Rendering (SSR) with Supabase, you need to configure your Supabase client to use cookies. The `@supabase/ssr` package helps you do this for JavaScript/TypeScript applications.
|
|
|
|
## Install
|
|
|
|
Install the `@supabase/ssr` and `@supabase/supabase-js` packages:
|
|
|
|
<Tabs size="small" type="underlined" queryGroup="package-manager" defaultActiveId="npm">
|
|
|
|
<TabPanel id="npm" label="npm">
|
|
|
|
```bash
|
|
npm install @supabase/ssr @supabase/supabase-js
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="yarn" label="yarn">
|
|
|
|
```bash
|
|
yarn add @supabase/ssr @supabase/supabase-js
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="pnpm" label="pnpm">
|
|
|
|
```bash
|
|
pnpm add @supabase/ssr @supabase/supabase-js
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
</Tabs>
|
|
|
|
## Set environment variables
|
|
|
|
In your environment variables file, set your Supabase URL and Supabase Anon Key:
|
|
|
|
<ProjectConfigVariables variable="url" />
|
|
<ProjectConfigVariables variable="anonKey" />
|
|
|
|
<Tabs scrollable size="small" type="underlined" defaultActiveId="nextjs" queryGroup="framework">
|
|
|
|
<TabPanel id="nextjs" label="Next.js">
|
|
|
|
```bash .env.local
|
|
NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
|
|
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_... or anon keyY
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="sveltekit" label="SvelteKit">
|
|
|
|
```bash .env.local
|
|
PUBLIC_SUPABASE_URL=your_supabase_project_url
|
|
PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_... or anon keyY
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="astro" label="Astro">
|
|
|
|
```bash .env
|
|
PUBLIC_SUPABASE_URL=your_supabase_project_url
|
|
PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_... or anon keyY
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="remix" label="Remix">
|
|
|
|
```bash .env
|
|
SUPABASE_URL=your_supabase_project_url
|
|
SUPABASE_PUBLISHABLE_KEY=sb_publishable_... or anon keyY
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="react-router" label="React Router">
|
|
|
|
```bash .env
|
|
SUPABASE_URL=your_supabase_project_url
|
|
SUPABASE_ANON_KEY=your_supabase_anon_key
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="express" label="Express">
|
|
|
|
```bash .env
|
|
SUPABASE_URL=your_supabase_project_url
|
|
SUPABASE_PUBLISHABLE_KEY=sb_publishable_... or anon keyY
|
|
```
|
|
|
|
Install [dotenv](https://www.npmjs.com/package/dotenv):
|
|
|
|
```bash
|
|
npm i dotenv
|
|
```
|
|
|
|
And initialize it:
|
|
|
|
<Tabs size="small" type="underlined" queryGroup="package-manager" defaultActiveId="npm">
|
|
|
|
<TabPanel id="npm" label="npm">
|
|
|
|
```bash
|
|
npm install dotenv
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="yarn" label="yarn">
|
|
|
|
```bash
|
|
yarn add dotenv
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="pnpm" label="pnpm">
|
|
|
|
```bash
|
|
pnpm add dotenv
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
</Tabs>
|
|
|
|
</TabPanel>
|
|
<TabPanel id="hono" label="Hono">
|
|
|
|
```bash .env
|
|
SUPABASE_URL=your_supabase_project_url
|
|
SUPABASE_PUBLISHABLE_KEY=sb_publishable_... or anon keyY
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
## Create a client
|
|
|
|
You'll need some one-time setup code to configure your Supabase client to use cookies. Once your utility code is set up, you can use your new `createClient` utility functions to get a properly configured Supabase client.
|
|
|
|
Use the browser client in code that runs on the browser, and the server client in code that runs on the server.
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="nextjs"
|
|
queryGroup="framework"
|
|
>
|
|
<TabPanel id="nextjs" label="Next.js">
|
|
|
|
The following code samples are for App Router. For help with Pages Router, see the [Next.js Server-Side Auth guide](/docs/guides/auth/server-side/nextjs?queryGroups=router&router=pages).
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="client-component"
|
|
queryGroup="environment"
|
|
>
|
|
<TabPanel id="client" label="Client-side">
|
|
|
|
```ts utils/supabase/client.ts
|
|
import { createBrowserClient } from '@supabase/ssr'
|
|
|
|
export function createClient() {
|
|
return createBrowserClient(
|
|
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!
|
|
)
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="server" label="Server-side">
|
|
|
|
```ts utils/supabase/server.ts
|
|
import { createServerClient } from '@supabase/ssr'
|
|
import { cookies } from 'next/headers'
|
|
|
|
export async function createClient() {
|
|
const cookieStore = await cookies()
|
|
|
|
return createServerClient(
|
|
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
|
|
{
|
|
cookies: {
|
|
getAll() {
|
|
return cookieStore.getAll()
|
|
},
|
|
setAll(cookiesToSet) {
|
|
try {
|
|
cookiesToSet.forEach(({ name, value, options }) =>
|
|
cookieStore.set(name, value, options)
|
|
)
|
|
} catch {
|
|
// The `setAll` method was called from a Server Component.
|
|
// This can be ignored if you have middleware refreshing
|
|
// user sessions.
|
|
}
|
|
},
|
|
},
|
|
}
|
|
)
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="middleware" label="Middleware">
|
|
|
|
In Next.js, because Server Components cannot set cookies, you'll also need a middleware client to handle cookie refreshes. The middleware should run before every route that needs access to Supabase, or that is protected by Supabase Auth.
|
|
|
|
```ts middleware.ts
|
|
import { type NextRequest } from 'next/server'
|
|
import { updateSession } from '@/utils/supabase/middleware'
|
|
|
|
export async function middleware(request: NextRequest) {
|
|
return await updateSession(request)
|
|
}
|
|
|
|
export const config = {
|
|
matcher: [
|
|
/*
|
|
* Match all request paths except for the ones starting with:
|
|
* - _next/static (static files)
|
|
* - _next/image (image optimization files)
|
|
* - favicon.ico (favicon file)
|
|
* Feel free to modify this pattern to include more paths.
|
|
*/
|
|
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
|
|
],
|
|
}
|
|
```
|
|
|
|
```ts utils/supabase/middleware.ts
|
|
import { createServerClient } from '@supabase/ssr'
|
|
import { NextResponse, type NextRequest } from 'next/server'
|
|
|
|
export async function updateSession(request: NextRequest) {
|
|
let supabaseResponse = NextResponse.next({
|
|
request,
|
|
})
|
|
|
|
const supabase = createServerClient(
|
|
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
|
|
{
|
|
cookies: {
|
|
getAll() {
|
|
return request.cookies.getAll()
|
|
},
|
|
setAll(cookiesToSet) {
|
|
cookiesToSet.forEach(({ name, value, options }) => request.cookies.set(name, value))
|
|
supabaseResponse = NextResponse.next({
|
|
request,
|
|
})
|
|
cookiesToSet.forEach(({ name, value, options }) =>
|
|
supabaseResponse.cookies.set(name, value, options)
|
|
)
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
// IMPORTANT: Avoid writing any logic between createServerClient and
|
|
// supabase.auth.getClaims(). A simple mistake could make it very hard to debug
|
|
// issues with users being randomly logged out.
|
|
|
|
// IMPORTANT: Don't remove getClaims()
|
|
const { data } = await supabase.auth.getClaims()
|
|
|
|
const user = data?.claims
|
|
|
|
if (
|
|
!user &&
|
|
!request.nextUrl.pathname.startsWith('/login') &&
|
|
!request.nextUrl.pathname.startsWith('/auth')
|
|
) {
|
|
// no user, potentially respond by redirecting the user to the login page
|
|
const url = request.nextUrl.clone()
|
|
url.pathname = '/login'
|
|
return NextResponse.redirect(url)
|
|
}
|
|
|
|
// IMPORTANT: You *must* return the supabaseResponse object as it is. If you're
|
|
// creating a new response object with NextResponse.next() make sure to:
|
|
// 1. Pass the request in it, like so:
|
|
// const myNewResponse = NextResponse.next({ request })
|
|
// 2. Copy over the cookies, like so:
|
|
// myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll())
|
|
// 3. Change the myNewResponse object to fit your needs, but avoid changing
|
|
// the cookies!
|
|
// 4. Finally:
|
|
// return myNewResponse
|
|
// If this is not done, you may be causing the browser and server to go out
|
|
// of sync and terminate the user's session prematurely!
|
|
|
|
return supabaseResponse
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
</TabPanel>
|
|
<TabPanel id="sveltekit" label="SvelteKit">
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="hooks"
|
|
queryGroup="environment"
|
|
>
|
|
<TabPanel id="hooks" label="Hooks">
|
|
|
|
```ts hooks.server.ts
|
|
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public'
|
|
import { createServerClient } from '@supabase/ssr'
|
|
import type { Handle } from '@sveltejs/kit'
|
|
|
|
export const handle: Handle = async ({ event, resolve }) => {
|
|
event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY, {
|
|
cookies: {
|
|
getAll() {
|
|
return event.cookies.getAll()
|
|
},
|
|
setAll(cookiesToSet) {
|
|
/**
|
|
* Note: You have to add the `path` variable to the
|
|
* set and remove method due to sveltekit's cookie API
|
|
* requiring this to be set, setting the path to an empty string
|
|
* will replicate previous/standard behavior (https://kit.svelte.dev/docs/types#public-types-cookies)
|
|
*/
|
|
cookiesToSet.forEach(({ name, value, options }) =>
|
|
event.cookies.set(name, value, { ...options, path: '/' })
|
|
)
|
|
},
|
|
},
|
|
})
|
|
|
|
/**
|
|
* Unlike `supabase.auth.getSession()`, which returns the session _without_
|
|
* validating the JWT, this function also calls `getUser()` to validate the
|
|
* JWT before returning the session.
|
|
*/
|
|
event.locals.safeGetSession = async () => {
|
|
const {
|
|
data: { session },
|
|
} = await event.locals.supabase.auth.getSession()
|
|
if (!session) {
|
|
return { session: null, user: null }
|
|
}
|
|
|
|
const {
|
|
data: { user },
|
|
error,
|
|
} = await event.locals.supabase.auth.getUser()
|
|
if (error) {
|
|
// JWT validation has failed
|
|
return { session: null, user: null }
|
|
}
|
|
|
|
return { session, user }
|
|
}
|
|
|
|
return resolve(event, {
|
|
filterSerializedResponseHeaders(name) {
|
|
return name === 'content-range' || name === 'x-supabase-api-version'
|
|
},
|
|
})
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="layout" label="Root Layout Load">
|
|
|
|
Page components can get access to the Supabase client from the `data` object due to this load function.
|
|
|
|
```ts +layout.ts
|
|
import { PUBLIC_SUPABASE_PUBLISHABLE_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
|
|
import type { LayoutLoad } from './$types'
|
|
import { createBrowserClient, createServerClient, isBrowser } from '@supabase/ssr'
|
|
|
|
export const load: LayoutLoad = async ({ fetch, data, depends }) => {
|
|
depends('supabase:auth')
|
|
|
|
const supabase = isBrowser()
|
|
? createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY, {
|
|
global: {
|
|
fetch,
|
|
},
|
|
})
|
|
: createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY, {
|
|
global: {
|
|
fetch,
|
|
},
|
|
cookies: {
|
|
getAll() {
|
|
return data.cookies
|
|
},
|
|
},
|
|
})
|
|
|
|
/**
|
|
* It's fine to use `getSession` here, because on the client, `getSession` is
|
|
* safe, and on the server, it reads `session` from the `LayoutData`, which
|
|
* safely checked the session using `safeGetSession`.
|
|
*/
|
|
const {
|
|
data: { session },
|
|
} = await supabase.auth.getSession()
|
|
|
|
return { supabase, session }
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="layout-server" label="Root Server Layout">
|
|
|
|
```ts +layout.server.ts
|
|
import type { LayoutServerLoad } from './$types'
|
|
|
|
export const load: LayoutServerLoad = async ({ locals: { safeGetSession }, cookies }) => {
|
|
const { session, user } = await safeGetSession()
|
|
|
|
return {
|
|
session,
|
|
user,
|
|
cookies: cookies.getAll(),
|
|
}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
</Tabs>
|
|
|
|
</TabPanel>
|
|
<TabPanel id="astro" label="Astro">
|
|
|
|
By default, Astro apps are static. This means the requests for data happen at build time, rather than when the user requests a page. At build time, there is no user, session or cookies. Therefore, we need to configure Astro for Server-side Rendering (SSR) if you want data to be fetched dynamically per request.
|
|
|
|
```js astro.config.mjs
|
|
import { defineConfig } from 'astro/config'
|
|
|
|
export default defineConfig({
|
|
output: 'server',
|
|
})
|
|
```
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="astro-server"
|
|
queryGroup="environment"
|
|
>
|
|
<TabPanel id="astro-server" label="Server">
|
|
|
|
```ts index.astro
|
|
---
|
|
import { createServerClient, parseCookieHeader } from "@supabase/ssr";
|
|
|
|
const supabase = createServerClient(
|
|
import.meta.env.PUBLIC_SUPABASE_URL,
|
|
import.meta.env.PUBLIC_SUPABASE_PUBLISHABLE_KEY,
|
|
{
|
|
cookies: {
|
|
getAll() {
|
|
return parseCookieHeader(Astro.request.headers.get('Cookie') ?? '')
|
|
},
|
|
setAll(cookiesToSet) {
|
|
cookiesToSet.forEach(({ name, value, options }) =>
|
|
Astro.cookies.set(name, value, options))
|
|
},
|
|
},
|
|
}
|
|
);
|
|
---
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="astro-browser" label="Browser">
|
|
|
|
```html index.astro
|
|
<script>
|
|
import { createBrowserClient } from "@supabase/ssr";
|
|
|
|
const supabase = createBrowserClient(
|
|
import.meta.env.PUBLIC_SUPABASE_URL,
|
|
import.meta.env.PUBLIC_SUPABASE_PUBLISHABLE_KEY
|
|
);
|
|
</script>
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="astro-server-endpoint" label="Server Endpoint">
|
|
|
|
```ts route.ts
|
|
import { createServerClient, parseCookieHeader } from "@supabase/ssr";
|
|
import type { APIContext } from "astro";
|
|
|
|
export async function GET(context: APIContext) {
|
|
const supabase = createServerClient(
|
|
import.meta.env.PUBLIC_SUPABASE_URL,
|
|
import.meta.env.PUBLIC_SUPABASE_PUBLISHABLE_KEY,
|
|
{
|
|
cookies: {
|
|
getAll() {
|
|
return parseCookieHeader(context.request.headers.get('Cookie') ?? '')
|
|
},
|
|
setAll(cookiesToSet) {
|
|
cookiesToSet.forEach(({ name, value, options }) =>
|
|
context.cookies.set(name, value, options))
|
|
},
|
|
},
|
|
}
|
|
);
|
|
|
|
return ...
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="astro-middleware" label="Middleware">
|
|
|
|
```ts middleware.ts
|
|
import { createServerClient, parseCookieHeader } from '@supabase/ssr'
|
|
import { defineMiddleware } from 'astro:middleware'
|
|
|
|
export const onRequest = defineMiddleware(async (context, next) => {
|
|
const supabase = createServerClient(
|
|
import.meta.env.PUBLIC_SUPABASE_URL,
|
|
import.meta.env.PUBLIC_SUPABASE_PUBLISHABLE_KEY,
|
|
{
|
|
cookies: {
|
|
getAll() {
|
|
return parseCookieHeader(context.request.headers.get('Cookie') ?? '')
|
|
},
|
|
setAll(cookiesToSet) {
|
|
cookiesToSet.forEach(({ name, value, options }) =>
|
|
context.cookies.set(name, value, options)
|
|
)
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
return next()
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
</TabPanel>
|
|
<TabPanel id="remix" label="Remix">
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="remix-loader"
|
|
queryGroup="environment"
|
|
>
|
|
<TabPanel id="remix-loader" label="Loader">
|
|
|
|
```ts _index.tsx
|
|
import { type LoaderFunctionArgs } from '@remix-run/node'
|
|
import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
|
|
|
|
export async function loader({ request }: LoaderFunctionArgs) {
|
|
const headers = new Headers()
|
|
|
|
const supabase = createServerClient(
|
|
process.env.SUPABASE_URL!,
|
|
process.env.SUPABASE_PUBLISHABLE_KEY!,
|
|
{
|
|
cookies: {
|
|
getAll() {
|
|
return parseCookieHeader(request.headers.get('Cookie') ?? '')
|
|
},
|
|
setAll(cookiesToSet) {
|
|
cookiesToSet.forEach(({ name, value, options }) =>
|
|
headers.append('Set-Cookie', serializeCookieHeader(name, value, options))
|
|
)
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
return new Response('...', {
|
|
headers,
|
|
})
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="remix-action" label="Action">
|
|
|
|
```ts _index.tsx
|
|
import { type ActionFunctionArgs } from '@remix-run/node'
|
|
import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
|
|
|
|
export async function action({ request }: ActionFunctionArgs) {
|
|
const headers = new Headers()
|
|
|
|
const supabase = createServerClient(
|
|
process.env.SUPABASE_URL!,
|
|
process.env.SUPABASE_PUBLISHABLE_KEY!,
|
|
{
|
|
cookies: {
|
|
getAll() {
|
|
return parseCookieHeader(request.headers.get('Cookie') ?? '')
|
|
},
|
|
setAll(cookiesToSet) {
|
|
cookiesToSet.forEach(({ name, value, options }) =>
|
|
headers.append('Set-Cookie', serializeCookieHeader(name, value, options))
|
|
)
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
return new Response('...', {
|
|
headers,
|
|
})
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="remix-component" label="Component">
|
|
|
|
```ts _index.tsx
|
|
import { type LoaderFunctionArgs } from "@remix-run/node";
|
|
import { useLoaderData } from "@remix-run/react";
|
|
import { createBrowserClient } from "@supabase/ssr";
|
|
|
|
export async function loader({}: LoaderFunctionArgs) {
|
|
return {
|
|
env: {
|
|
SUPABASE_URL: process.env.SUPABASE_URL!,
|
|
SUPABASE_PUBLISHABLE_KEY: process.env.SUPABASE_PUBLISHABLE_KEY!,
|
|
},
|
|
};
|
|
}
|
|
|
|
export default function Index() {
|
|
const { env } = useLoaderData<typeof loader>();
|
|
|
|
const supabase = createBrowserClient(env.SUPABASE_URL, env.SUPABASE_PUBLISHABLE_KEY);
|
|
|
|
return ...
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="react-router" label="React Router">
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="react-router-loader"
|
|
queryGroup="environment"
|
|
>
|
|
<TabPanel id="react-router-loader" label="Loader">
|
|
|
|
```ts _index.tsx
|
|
import { LoaderFunctionArgs } from 'react-router'
|
|
import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
|
|
|
|
export async function loader({ request }: LoaderFunctionArgs) {
|
|
const headers = new Headers()
|
|
|
|
const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {
|
|
cookies: {
|
|
getAll() {
|
|
return parseCookieHeader(request.headers.get('Cookie') ?? '')
|
|
},
|
|
setAll(cookiesToSet) {
|
|
cookiesToSet.forEach(({ name, value, options }) =>
|
|
headers.append('Set-Cookie', serializeCookieHeader(name, value, options))
|
|
)
|
|
},
|
|
},
|
|
})
|
|
|
|
return new Response('...', {
|
|
headers,
|
|
})
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="react-router-action" label="Action">
|
|
|
|
```ts _index.tsx
|
|
import { type ActionFunctionArgs } from '@react-router'
|
|
import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
|
|
|
|
export async function action({ request }: ActionFunctionArgs) {
|
|
const headers = new Headers()
|
|
|
|
const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {
|
|
cookies: {
|
|
getAll() {
|
|
return parseCookieHeader(request.headers.get('Cookie') ?? '')
|
|
},
|
|
setAll(cookiesToSet) {
|
|
cookiesToSet.forEach(({ name, value, options }) =>
|
|
headers.append('Set-Cookie', serializeCookieHeader(name, value, options))
|
|
)
|
|
},
|
|
},
|
|
})
|
|
|
|
return new Response('...', {
|
|
headers,
|
|
})
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="react-router-component" label="Component">
|
|
|
|
```ts _index.tsx
|
|
import { type LoaderFunctionArgs } from "react-router";
|
|
import { useLoaderData } from "react-router";
|
|
import { createBrowserClient } from "@supabase/ssr";
|
|
|
|
export async function loader({}: LoaderFunctionArgs) {
|
|
return {
|
|
env: {
|
|
SUPABASE_URL: process.env.SUPABASE_URL!,
|
|
SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY!,
|
|
},
|
|
};
|
|
}
|
|
|
|
export default function Index() {
|
|
const { env } = useLoaderData<typeof loader>();
|
|
|
|
const supabase = createBrowserClient(env.SUPABASE_URL, env.SUPABASE_ANON_KEY);
|
|
|
|
return ...
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="express" label="Express">
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="server-client"
|
|
queryGroup="environment"
|
|
>
|
|
<TabPanel id="server-client" label="Server Client">
|
|
|
|
```ts lib/supabase.js
|
|
const { createServerClient, parseCookieHeader, serializeCookieHeader } = require('@supabase/ssr')
|
|
|
|
exports.createClient = (context) => {
|
|
return createServerClient(process.env.SUPABASE_URL, process.env.SUPABASE_PUBLISHABLE_KEY, {
|
|
cookies: {
|
|
getAll() {
|
|
return parseCookieHeader(context.req.headers.cookie ?? '')
|
|
},
|
|
setAll(cookiesToSet) {
|
|
cookiesToSet.forEach(({ name, value, options }) =>
|
|
context.res.appendHeader('Set-Cookie', serializeCookieHeader(name, value, options))
|
|
)
|
|
},
|
|
},
|
|
})
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="express-route" label="Route">
|
|
|
|
```ts app.js
|
|
const express = require("express")
|
|
const dotenv = require("dotenv")
|
|
|
|
const { createClient } = require("./lib/supabase")
|
|
|
|
const app = express()
|
|
|
|
app.post("/hello-world", async function (req, res, next) {
|
|
const { email, emailConfirm } = req.body
|
|
...
|
|
|
|
const supabase = createClient({ req, res })
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="hono" label="Hono">
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="server-client"
|
|
queryGroup="environment"
|
|
>
|
|
<TabPanel id="server-client" label="Server Client">
|
|
|
|
Create a Hono middleware that creates a Supabase client.
|
|
|
|
```ts middleware/auth.middleware.ts
|
|
import { createServerClient, parseCookieHeader } from '@supabase/ssr'
|
|
import { SupabaseClient } from '@supabase/supabase-js'
|
|
import type { Context, MiddlewareHandler } from 'hono'
|
|
import { env } from 'hono/adapter'
|
|
import { setCookie } from 'hono/cookie'
|
|
|
|
declare module 'hono' {
|
|
interface ContextVariableMap {
|
|
supabase: SupabaseClient
|
|
}
|
|
}
|
|
|
|
export const getSupabase = (c: Context) => {
|
|
return c.get('supabase')
|
|
}
|
|
|
|
type SupabaseEnv = {
|
|
SUPABASE_URL: string
|
|
SUPABASE_PUBLISHABLE_KEY: string
|
|
}
|
|
|
|
export const supabaseMiddleware = (): MiddlewareHandler => {
|
|
return async (c, next) => {
|
|
const supabaseEnv = env<SupabaseEnv>(c)
|
|
const supabaseUrl = supabaseEnv.SUPABASE_URL
|
|
const supabaseAnonKey = supabaseEnv.SUPABASE_PUBLISHABLE_KEY
|
|
|
|
if (!supabaseUrl) {
|
|
throw new Error('SUPABASE_URL missing!')
|
|
}
|
|
|
|
if (!supabaseAnonKey) {
|
|
throw new Error('SUPABASE_PUBLISHABLE_KEY missing!')
|
|
}
|
|
|
|
const supabase = createServerClient(supabaseUrl, supabaseAnonKey, {
|
|
cookies: {
|
|
getAll() {
|
|
return parseCookieHeader(c.req.header('Cookie') ?? '')
|
|
},
|
|
setAll(cookiesToSet) {
|
|
cookiesToSet.forEach(({ name, value, options }) => setCookie(c, name, value, options))
|
|
},
|
|
},
|
|
})
|
|
|
|
c.set('supabase', supabase)
|
|
|
|
await next()
|
|
}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="hono-route" label="Route">
|
|
|
|
You can now use this middleware in your Hono application to create a server Supabase client that can be used to make authenticated requests.
|
|
|
|
```ts index.tsx
|
|
import { Hono } from 'hono'
|
|
import { getSupabase, supabaseMiddleware } from './middleware/auth.middleware'
|
|
|
|
const app = new Hono()
|
|
app.use('*', supabaseMiddleware())
|
|
|
|
app.get('/api/user', async (c) => {
|
|
const supabase = getSupabase(c)
|
|
const { data, error } = await supabase.auth.getUser()
|
|
|
|
if (error) console.log('error', error)
|
|
|
|
if (!data?.user) {
|
|
return c.json({
|
|
message: 'You are not logged in.',
|
|
})
|
|
}
|
|
|
|
return c.json({
|
|
message: 'You are logged in!',
|
|
userId: data.user,
|
|
})
|
|
})
|
|
|
|
app.get('/signout', async (c) => {
|
|
const supabase = getSupabase(c)
|
|
await supabase.auth.signOut()
|
|
console.log('Signed out server-side!')
|
|
return c.redirect('/')
|
|
})
|
|
|
|
// Retrieve data with RLS enabled. The signed in user's auth token is automatically sent.
|
|
app.get('/countries', async (c) => {
|
|
const supabase = getSupabase(c)
|
|
const { data, error } = await supabase.from('countries').select('*')
|
|
if (error) console.log(error)
|
|
return c.json(data)
|
|
})
|
|
|
|
export default app
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
## Next steps
|
|
|
|
- Implement [Authentication using Email and Password](/docs/guides/auth/server-side/email-based-auth-with-pkce-flow-for-ssr)
|
|
- Implement [Authentication using OAuth](/docs/guides/auth/server-side/oauth-with-pkce-flow-for-ssr)
|
|
- [Learn more about SSR](/docs/guides/auth/server-side-rendering)
|