mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
Delete
This commit is contained in:
6 files changed
-5796
No files matched your search
@@ -1,492 +0,0 @@
|
||||
---
|
||||
id: 'auth-ui'
|
||||
title: 'Auth UI'
|
||||
description: 'A prebuilt, customizable React component for authenticating users.'
|
||||
sitemapPriority: 0.3
|
||||
---
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
As of 7th Feb 2024, [this repository](https://github.com/supabase-community/auth-ui) is no longer maintained by the Supabase Team. At the moment, the team does not have capacity to give the expected level of care to this repository. We may revisit Auth UI in the future but regrettably have to leave it on hold for now as we focus on other priorities such as improving the Server-Side Rendering (SSR) package and advanced Auth primitives.
|
||||
|
||||
As an alternative you can use the [Supabase UI Library](/ui) which has auth ready blocks to use in your projects.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Auth UI is a pre-built React component for authenticating users.
|
||||
It supports custom themes and extensible styles to match your brand and aesthetic.
|
||||
|
||||
<video width="99%" muted playsInline controls={true}>
|
||||
<source src="https://supabase.com/images/blog/lw5-one-more/auth-ui-demo.mp4" type="video/mp4" />
|
||||
</video>
|
||||
|
||||
## Set up Auth UI
|
||||
|
||||
Install the latest version of [supabase-js](/docs/reference/javascript) and the Auth UI package:
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js @supabase/auth-ui-react @supabase/auth-ui-shared
|
||||
```
|
||||
|
||||
### Import the Auth component
|
||||
|
||||
Pass `supabaseClient` from `@supabase/supabase-js` as a prop to the component.
|
||||
|
||||
```js /src/index.js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { Auth } from '@supabase/auth-ui-react'
|
||||
|
||||
const supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
|
||||
|
||||
const App = () => <Auth supabaseClient={supabase} />
|
||||
```
|
||||
|
||||
This renders the Auth component without any styling.
|
||||
We recommend using one of the predefined themes to style the UI.
|
||||
Import the theme you want to use and pass it to the `appearance.theme` prop.
|
||||
|
||||
```js
|
||||
import { Auth } from '@supabase/auth-ui-react'
|
||||
import {
|
||||
// Import predefined theme
|
||||
ThemeSupa,
|
||||
} from '@supabase/auth-ui-shared'
|
||||
|
||||
const supabase = createClient(
|
||||
'<INSERT PROJECT URL>',
|
||||
'<INSERT PROJECT ANON API KEY>'
|
||||
)
|
||||
|
||||
const App = () => (
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
{/* Apply predefined theme */}
|
||||
appearance={{ theme: ThemeSupa }}
|
||||
/>
|
||||
)
|
||||
```
|
||||
|
||||
### Social providers
|
||||
|
||||
The Auth component also supports login with [official social providers](../../auth#providers).
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { Auth } from '@supabase/auth-ui-react'
|
||||
import { ThemeSupa } from '@supabase/auth-ui-shared'
|
||||
|
||||
const supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
|
||||
|
||||
const App = () => (
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
appearance={{ theme: ThemeSupa }}
|
||||
providers={['google', 'facebook', 'twitter']}
|
||||
/>
|
||||
)
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
Options are available via `queryParams`:
|
||||
|
||||
```jsx
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
providers={['google']}
|
||||
queryParams={{
|
||||
access_type: 'offline',
|
||||
prompt: 'consent',
|
||||
hd: 'domain.com',
|
||||
}}
|
||||
onlyThirdPartyProviders
|
||||
/>
|
||||
```
|
||||
|
||||
### Provider scopes
|
||||
|
||||
Provider Scopes can be requested through `providerScope`;
|
||||
|
||||
```jsx
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
providers={['google']}
|
||||
queryParams={{
|
||||
access_type: 'offline',
|
||||
prompt: 'consent',
|
||||
hd: 'domain.com',
|
||||
}}
|
||||
providerScopes={{
|
||||
google: 'https://www.googleapis.com/auth/calendar.readonly',
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
### Supported views
|
||||
|
||||
The Auth component is currently shipped with the following views:
|
||||
|
||||
- [Email Login](../auth-email)
|
||||
- [Magic Link login](../auth-magic-link)
|
||||
- [Social Login](../social-login)
|
||||
- Update password
|
||||
- Forgotten password
|
||||
|
||||
We are planning on adding more views in the future. Follow along on that [repo](https://github.com/supabase/auth-ui).
|
||||
|
||||
## Customization
|
||||
|
||||
There are several ways to customize Auth UI:
|
||||
|
||||
- Use one of the [predefined themes](#predefined-themes) that comes with Auth UI
|
||||
- Extend a theme by [overriding the variable tokens](#override-themes) in a theme
|
||||
- [Create your own theme](#create-theme)
|
||||
- [Use your own CSS classes](#custom-css-classes)
|
||||
- [Use inline styles](#custom-inline-styles)
|
||||
- [Use your own labels](#custom-labels)
|
||||
|
||||
### Predefined themes
|
||||
|
||||
Auth UI comes with several themes to customize the appearance. Each predefined theme comes with at least two variations, a `default` variation, and a `dark` variation. You can switch between these themes using the `theme` prop. Import the theme you want to use and pass it to the `appearance.theme` prop.
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { Auth } from '@supabase/auth-ui-react'
|
||||
import { ThemeSupa } from '@supabase/auth-ui-shared'
|
||||
|
||||
const supabase = createClient(
|
||||
'<INSERT PROJECT URL>',
|
||||
'<INSERT PROJECT ANON API KEY>'
|
||||
)
|
||||
|
||||
const App = () => (
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
{/* Apply predefined theme */}
|
||||
appearance={{ theme: ThemeSupa }}
|
||||
/>
|
||||
)
|
||||
```
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
Currently there is only one predefined theme available, but we plan to add more.
|
||||
|
||||
</Admonition>
|
||||
|
||||
### Switch theme variations
|
||||
|
||||
Auth UI comes with two theme variations: `default` and `dark`. You can switch between these themes with the `theme` prop.
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { Auth } from '@supabase/auth-ui-react'
|
||||
import { ThemeSupa } from '@supabase/auth-ui-shared'
|
||||
|
||||
const supabase = createClient(
|
||||
'<INSERT PROJECT URL>',
|
||||
'<INSERT PROJECT ANON API KEY>'
|
||||
)
|
||||
|
||||
const App = () => (
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
appearance={{ theme: ThemeSupa }}
|
||||
{/* Set theme to dark */}
|
||||
theme="dark"
|
||||
/>
|
||||
)
|
||||
```
|
||||
|
||||
If you don't pass a value to `theme` it uses the `"default"` theme. You can pass `"dark"` to the theme prop to switch to the `dark` theme. If your theme has other variations, use the name of the variation in this prop.
|
||||
|
||||
### Override themes
|
||||
|
||||
Auth UI themes can be overridden using variable tokens. See the [list of variable tokens](https://github.com/supabase/auth-ui/blob/main/packages/shared/src/theming/Themes.ts).
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { Auth } from '@supabase/auth-ui-react'
|
||||
import { ThemeSupa } from '@supabase/auth-ui-shared'
|
||||
|
||||
const supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
|
||||
|
||||
const App = () => (
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
appearance={{
|
||||
theme: ThemeSupa,
|
||||
variables: {
|
||||
default: {
|
||||
colors: {
|
||||
brand: 'red',
|
||||
brandAccent: 'darkred',
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
```
|
||||
|
||||
If you created your own theme, you may not need to override any of them.
|
||||
|
||||
### Create your own theme [#create-theme]
|
||||
|
||||
You can create your own theme by following the same structure within a `appearance.theme` property.
|
||||
See the list of [tokens within a theme](https://github.com/supabase/auth-ui/blob/main/packages/shared/src/theming/Themes.ts).
|
||||
|
||||
```js /src/index.js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { Auth } from '@supabase/auth-ui-react'
|
||||
|
||||
const supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
|
||||
|
||||
const customTheme = {
|
||||
default: {
|
||||
colors: {
|
||||
brand: 'hsl(153 60.0% 53.0%)',
|
||||
brandAccent: 'hsl(154 54.8% 45.1%)',
|
||||
brandButtonText: 'white',
|
||||
// ..
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
colors: {
|
||||
brandButtonText: 'white',
|
||||
defaultButtonBackground: '#2e2e2e',
|
||||
defaultButtonBackgroundHover: '#3e3e3e',
|
||||
//..
|
||||
},
|
||||
},
|
||||
// You can also add more theme variations with different names.
|
||||
evenDarker: {
|
||||
colors: {
|
||||
brandButtonText: 'white',
|
||||
defaultButtonBackground: '#1e1e1e',
|
||||
defaultButtonBackgroundHover: '#2e2e2e',
|
||||
//..
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const App = () => (
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
theme="default" // can also be "dark" or "evenDarker"
|
||||
appearance={{ theme: customTheme }}
|
||||
/>
|
||||
)
|
||||
```
|
||||
|
||||
You can switch between different variations of your theme with the ["theme" prop](#switch-theme-variations).
|
||||
|
||||
### Custom CSS classes [#custom-css-classes]
|
||||
|
||||
You can use custom CSS classes for the following elements:
|
||||
`"button"`, `"container"`, `"anchor"`, `"divider"`, `"label"`, `"input"`, `"loader"`, `"message"`.
|
||||
|
||||
```js /src/index.js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { Auth } from '@supabase/auth-ui-react'
|
||||
|
||||
const supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
|
||||
|
||||
const App = () => (
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
appearance={{
|
||||
// If you want to extend the default styles instead of overriding it, set this to true
|
||||
extend: false,
|
||||
// Your custom classes
|
||||
className: {
|
||||
anchor: 'my-awesome-anchor',
|
||||
button: 'my-awesome-button',
|
||||
//..
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
```
|
||||
|
||||
### Custom inline CSS [#custom-inline-styles]
|
||||
|
||||
You can use custom CSS inline styles for the following elements:
|
||||
`"button"`, `"container"`, `"anchor"`, `"divider"`, `"label"`, `"input"`, `"loader"`, `"message"`.
|
||||
|
||||
```js /src/index.js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { Auth } from '@supabase/auth-ui-react'
|
||||
|
||||
const supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
|
||||
|
||||
const App = () => (
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
appearance={{
|
||||
style: {
|
||||
button: { background: 'red', color: 'white' },
|
||||
anchor: { color: 'blue' },
|
||||
//..
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
```
|
||||
|
||||
### Custom labels [#custom-labels]
|
||||
|
||||
You can use custom labels with `localization.variables` like so:
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { Auth } from '@supabase/auth-ui-react'
|
||||
|
||||
const supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
|
||||
|
||||
const App = () => (
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
localization={{
|
||||
variables: {
|
||||
sign_in: {
|
||||
email_label: 'Your email address',
|
||||
password_label: 'Your strong password',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
```
|
||||
|
||||
A full list of the available variables is below:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="sign-up"
|
||||
>
|
||||
<TabPanel id="sign-up" label="Sign Up">
|
||||
|
||||
| Label Tag | Default Label |
|
||||
| ---------------------------- | ------------------------------------------ |
|
||||
| `email_label` | Email address |
|
||||
| `password_label` | Create a Password |
|
||||
| `email_input_placeholder` | Your email address |
|
||||
| `password_input_placeholder` | Your password |
|
||||
| `button_label` | Sign up |
|
||||
| `loading_button_label` | Signing up ... |
|
||||
| `social_provider_text` | Sign in with `{{provider}}` |
|
||||
| `link_text` | Don't have an account? Sign up |
|
||||
| `confirmation_text` | Check your email for the confirmation link |
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="sign-in" label="Sign In">
|
||||
|
||||
| Label Tag | Default Label |
|
||||
| ---------------------------- | -------------------------------- |
|
||||
| `email_label` | Email address |
|
||||
| `password_label` | Your Password |
|
||||
| `email_input_placeholder` | Your email address |
|
||||
| `password_input_placeholder` | Your password |
|
||||
| `button_label` | Sign in |
|
||||
| `loading_button_label` | Signing in ... |
|
||||
| `social_provider_text` | Sign in with `{{provider}}` |
|
||||
| `link_text` | Already have an account? Sign in |
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="magic_link" label="Magic Link">
|
||||
|
||||
| Label Tag | Default Label |
|
||||
| ------------------------- | ----------------------------------- |
|
||||
| `email_input_label` | Email address |
|
||||
| `email_input_placeholder` | Your email address |
|
||||
| `button_label` | Sign in |
|
||||
| `loading_button_label` | Signing in ... |
|
||||
| `link_text` | Send a magic link email |
|
||||
| `confirmation_text` | Check your email for the magic link |
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="forgotten-password" label="Forgotten Password">
|
||||
|
||||
| Label Tag | Default Label |
|
||||
| ------------------------- | -------------------------------------------- |
|
||||
| `email_label` | Email address |
|
||||
| `password_label` | Your Password |
|
||||
| `email_input_placeholder` | Your email address |
|
||||
| `button_label` | Send reset password instructions |
|
||||
| `loading_button_label` | Sending reset instructions ... |
|
||||
| `link_text` | Forgot your password? |
|
||||
| `confirmation_text` | Check your email for the password reset link |
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="update-password" label="Update Password">
|
||||
|
||||
| Label Tag | Default Label |
|
||||
| ---------------------------- | ------------------------------ |
|
||||
| `password_label` | New Password |
|
||||
| `password_input_placeholder` | Your new password |
|
||||
| `button_label` | Update password |
|
||||
| `loading_button_label` | Updating password ... |
|
||||
| `confirmation_text` | Your password has been updated |
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="verify-otp" label="Verify OTP">
|
||||
|
||||
| Label Tag | Default Label |
|
||||
| ------------------------- | ------------------ |
|
||||
| `email_input_label` | Email address |
|
||||
| `email_input_placeholder` | Your email address |
|
||||
| `phone_input_label` | Phone number |
|
||||
| `phone_input_placeholder` | Your phone number |
|
||||
| `token_input_label` | Token |
|
||||
| `token_input_placeholder` | Your OTP token |
|
||||
| `button_label` | Verify token |
|
||||
| `loading_button_label` | Signing in ... |
|
||||
|
||||
</TabPanel>
|
||||
|
||||
</Tabs>
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
Currently, translating error messages (e.g. "Invalid credentials") is not supported. Check [related issue.](https://github.com/supabase-community/auth-ui/issues/86)
|
||||
|
||||
</Admonition>
|
||||
|
||||
### Hiding links [#hiding-links]
|
||||
|
||||
You can hide links by setting the `showLinks` prop to `false`
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { Auth } from '@supabase/auth-ui-react'
|
||||
|
||||
const supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
|
||||
|
||||
const App = () => <Auth supabaseClient={supabase} showLinks={false} />
|
||||
```
|
||||
|
||||
Setting `showLinks` to `false` will hide the following links:
|
||||
|
||||
- Don't have an account? Sign up
|
||||
- Already have an account? Sign in
|
||||
- Send a magic link email
|
||||
- Forgot your password?
|
||||
|
||||
### Sign in and sign up views
|
||||
|
||||
Add `sign_in` or `sign_up` views with the `view` prop:
|
||||
|
||||
```
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
view="sign_up"
|
||||
/>
|
||||
```
|
||||
@@ -1,122 +0,0 @@
|
||||
---
|
||||
id: 'flutter-auth-ui'
|
||||
title: 'Flutter Auth UI'
|
||||
description: 'Prebuilt, customizable Flutter widgets for authenticating users.'
|
||||
sitemapPriority: 0.3
|
||||
---
|
||||
|
||||
Flutter Auth UI is a Flutter package containing pre-built widgets for authenticating users.
|
||||
It is unstyled and can match your brand and aesthetic.
|
||||
|
||||

|
||||
|
||||
## Add Flutter Auth UI
|
||||
|
||||
Add the latest version of the package [supabase-auth-ui](https://pub.dev/packages/supabase_auth_ui) to pubspec.yaml:
|
||||
|
||||
```bash
|
||||
flutter pub add supabase_auth_ui
|
||||
```
|
||||
|
||||
### Initialize the Flutter Auth package
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:supabase_auth_ui/supabase_auth_ui.dart';
|
||||
|
||||
void main() async {
|
||||
await Supabase.initialize(
|
||||
url: dotenv.get('SUPABASE_URL'),
|
||||
anonKey: dotenv.get('SUPABASE_PUBLISHABLE_KEY'),
|
||||
);
|
||||
|
||||
runApp(const MyApp());
|
||||
}
|
||||
```
|
||||
|
||||
### Email Auth
|
||||
|
||||
Use a `SupaEmailAuth` widget to create an email and password signin and signup form. It also contains a button to toggle to display a forgot password form.
|
||||
|
||||
You can pass `metadataFields` to add additional fields to the form to pass as metadata to Supabase.
|
||||
|
||||
```dart
|
||||
SupaEmailAuth(
|
||||
redirectTo: kIsWeb ? null : 'io.mydomain.myapp://callback',
|
||||
onSignInComplete: (response) {},
|
||||
onSignUpComplete: (response) {},
|
||||
metadataFields: [
|
||||
MetaDataField(
|
||||
prefixIcon: const Icon(Icons.person),
|
||||
label: 'Username',
|
||||
key: 'username',
|
||||
validator: (val) {
|
||||
if (val == null || val.isEmpty) {
|
||||
return 'Please enter something';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
### Magic link Auth
|
||||
|
||||
Use `SupaMagicAuth` widget to create a magic link signIn form.
|
||||
|
||||
```dart
|
||||
SupaMagicAuth(
|
||||
redirectUrl: kIsWeb ? null : 'io.mydomain.myapp://callback',
|
||||
onSuccess: (Session response) {},
|
||||
onError: (error) {},
|
||||
)
|
||||
```
|
||||
|
||||
### Reset password
|
||||
|
||||
Use `SupaResetPassword` to create a password reset form.
|
||||
|
||||
```dart
|
||||
SupaResetPassword(
|
||||
accessToken: supabase.auth.currentSession?.accessToken,
|
||||
onSuccess: (UserResponse response) {},
|
||||
onError: (error) {},
|
||||
)
|
||||
```
|
||||
|
||||
### Phone Auth
|
||||
|
||||
Use `SupaPhoneAuth` to create a phone authentication form.
|
||||
|
||||
```dart
|
||||
SupaPhoneAuth(
|
||||
authAction: SupaAuthAction.signUp,
|
||||
onSuccess: (AuthResponse response) {},
|
||||
),
|
||||
```
|
||||
|
||||
### Social Auth
|
||||
|
||||
The package supports login with [official social providers](../../auth#providers).
|
||||
|
||||
Use `SupaSocialsAuth` to create list of social login buttons.
|
||||
|
||||
```dart
|
||||
SupaSocialsAuth(
|
||||
socialProviders: [
|
||||
OAuthProvider.apple,
|
||||
OAuthProvider.google,
|
||||
],
|
||||
colored: true,
|
||||
redirectUrl: kIsWeb
|
||||
? null
|
||||
: 'io.mydomain.myapp://callback',
|
||||
onSuccess: (Session response) {},
|
||||
onError: (error) {},
|
||||
)
|
||||
```
|
||||
|
||||
### Theming
|
||||
|
||||
This package uses plain Flutter components allowing you to control the appearance of the components using your own theme.
|
||||
@@ -1,941 +0,0 @@
|
||||
---
|
||||
id: 'nextjs-pages'
|
||||
title: 'Supabase Auth with Next.js Pages Directory'
|
||||
description: 'Authentication helpers for Next.js API routes, middleware, and SSR in the Pages Directory.'
|
||||
sidebar_label: 'Next.js (pages)'
|
||||
sitemapPriority: 0.3
|
||||
---
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
The Auth helpers package is deprecated. Use the new `@supabase/ssr` package for Server Side Authentication. `@supabase/ssr` takes the core concepts of the Auth Helpers package and makes them available to any server framework. Read the [migration doc](/docs/guides/auth/server-side/migrating-to-ssr-from-auth-helpers) to learn more.
|
||||
|
||||
We recommend setting up Auth for your Next.js app with `@supabase/ssr` instead. Read the [Next.js Server-Side Auth guide](/docs/guides/auth/server-side/nextjs?router=pages) to learn how.
|
||||
|
||||
</Admonition>
|
||||
|
||||
<Accordion
|
||||
type="default"
|
||||
openBehaviour="multiple"
|
||||
chevronAlign="right"
|
||||
justified
|
||||
size="medium"
|
||||
className="text-foreground-light border-b mt-8 pb-2"
|
||||
>
|
||||
|
||||
<AccordionItem
|
||||
header="See legacy docs"
|
||||
id="legacy-docs"
|
||||
>
|
||||
|
||||
This submodule provides convenience helpers for implementing user authentication in Next.js applications using the pages directory.
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
Note: As of [Next.js 13.4](https://nextjs.org/blog/next-13-4), the App Router has reached stable status. This is now the recommended path for new Next.js app. Check out our guide on using [Auth Helpers with the Next.js App Directory](/docs/guides/auth/auth-helpers/nextjs).
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Install the Next.js helper library
|
||||
|
||||
```sh Terminal
|
||||
npm install @supabase/auth-helpers-nextjs @supabase/supabase-js
|
||||
```
|
||||
|
||||
This library supports the following tooling versions:
|
||||
|
||||
- Node.js: `^10.13.0 || >=12.0.0`
|
||||
- Next.js: `>=10`
|
||||
|
||||
Additionally, install the **React Auth Helpers** for components and hooks that can be used across all React-based frameworks.
|
||||
|
||||
```sh Terminal
|
||||
npm install @supabase/auth-helpers-react
|
||||
```
|
||||
|
||||
## Set up environment variables
|
||||
|
||||
Retrieve your project URL and anon key in your project's [API settings](/dashboard/project/_/settings/api) in the Dashboard to set up the following environment variables. For local development you can set them in a `.env.local` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/nextjs/.env.local.example).
|
||||
|
||||
```bash .env.local
|
||||
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
|
||||
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-supabase-publishable-key
|
||||
```
|
||||
|
||||
## Basic setup
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
queryGroup="language"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
Wrap your `pages/_app.js` component with the `SessionContextProvider` component:
|
||||
|
||||
```jsx pages/_app.js
|
||||
import { createPagesBrowserClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { SessionContextProvider } from '@supabase/auth-helpers-react'
|
||||
import { useState } from 'react'
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
// Create a new supabase browser client on every first render.
|
||||
const [supabaseClient] = useState(() => createPagesBrowserClient())
|
||||
|
||||
return (
|
||||
<SessionContextProvider
|
||||
supabaseClient={supabaseClient}
|
||||
initialSession={pageProps.initialSession}
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
</SessionContextProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
Wrap your `pages/_app.tsx` component with the `SessionContextProvider` component:
|
||||
|
||||
```tsx
|
||||
import { type AppProps } from 'next/app'
|
||||
import { createPagesBrowserClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { SessionContextProvider, Session } from '@supabase/auth-helpers-react'
|
||||
import { useState } from 'react'
|
||||
|
||||
function MyApp({
|
||||
Component,
|
||||
pageProps,
|
||||
}: AppProps<{
|
||||
initialSession: Session
|
||||
}>) {
|
||||
// Create a new supabase browser client on every first render.
|
||||
const [supabaseClient] = useState(() => createPagesBrowserClient())
|
||||
|
||||
return (
|
||||
<SessionContextProvider
|
||||
supabaseClient={supabaseClient}
|
||||
initialSession={pageProps.initialSession}
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
</SessionContextProvider>
|
||||
)
|
||||
}
|
||||
export default MyApp
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
You can now determine if a user is authenticated by checking that the `user` object returned by the `useUser()` hook is defined.
|
||||
|
||||
### Code Exchange API route
|
||||
|
||||
The `Code Exchange` API route is required for the [server-side auth flow](/docs/guides/auth/server-side-rendering) implemented by the Next.js Auth Helpers. It exchanges an auth `code` for the user's `session`, which is set as a cookie for future requests made to Supabase.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
queryGroup="language"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
Create a new file at `pages/api/auth/callback.js` and populate with the following:
|
||||
|
||||
```jsx pages/api/auth/callback.js
|
||||
import { NextApiHandler } from 'next'
|
||||
import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
const handler = async (req, res) => {
|
||||
const { code } = req.query
|
||||
|
||||
if (code) {
|
||||
const supabase = createPagesServerClient({ req, res })
|
||||
await supabase.auth.exchangeCodeForSession(String(code))
|
||||
}
|
||||
|
||||
res.redirect('/')
|
||||
}
|
||||
|
||||
export default handler
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
Create a new file at `pages/api/auth/callback.ts` and populate with the following:
|
||||
|
||||
```tsx pages/api/auth/callback.ts
|
||||
import { NextApiHandler } from 'next'
|
||||
import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
const handler: NextApiHandler = async (req, res) => {
|
||||
const { code } = req.query
|
||||
|
||||
if (code) {
|
||||
const supabase = createPagesServerClient({ req, res })
|
||||
await supabase.auth.exchangeCodeForSession(String(code))
|
||||
}
|
||||
|
||||
res.redirect('/')
|
||||
}
|
||||
|
||||
export default handler
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Usage with TypeScript
|
||||
|
||||
You can pass types that were [generated with the Supabase CLI](/docs/reference/javascript/typescript-support#generating-types) to the Supabase Client to get enhanced type safety and auto completion:
|
||||
|
||||
### Browser client
|
||||
|
||||
Creating a new `supabase` client object:
|
||||
|
||||
```tsx
|
||||
import { createPagesBrowserClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { Database } from '../database.types'
|
||||
|
||||
const supabaseClient = createPagesBrowserClient<Database>()
|
||||
```
|
||||
|
||||
Retrieving a `supabase` client object from the `SessionContext`:
|
||||
|
||||
```tsx
|
||||
import { useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
import { Database } from '../database.types'
|
||||
|
||||
const supabaseClient = useSupabaseClient<Database>()
|
||||
```
|
||||
|
||||
### Server client
|
||||
|
||||
```tsx
|
||||
// Creating a new supabase server client object (e.g. in API route):
|
||||
import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import type { Database } from 'types_db'
|
||||
|
||||
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const supabaseServerClient = createPagesServerClient<Database>({
|
||||
req,
|
||||
res,
|
||||
})
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabaseServerClient.auth.getUser()
|
||||
|
||||
res.status(200).json({ name: user?.name ?? '' })
|
||||
}
|
||||
```
|
||||
|
||||
## Client-side data fetching with RLS
|
||||
|
||||
For [row level security](/docs/learn/auth-deep-dive/auth-row-level-security) to work properly when fetching data client-side, you need to make sure to use the `supabaseClient` from the `useSupabaseClient` hook and only run your query once the user is defined client-side in the `useUser()` hook:
|
||||
|
||||
```jsx
|
||||
import { Auth } from '@supabase/auth-ui-react'
|
||||
import { ThemeSupa } from '@supabase/auth-ui-shared'
|
||||
import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
const LoginPage = () => {
|
||||
const supabaseClient = useSupabaseClient()
|
||||
const user = useUser()
|
||||
const [data, setData] = useState()
|
||||
|
||||
useEffect(() => {
|
||||
async function loadData() {
|
||||
const { data } = await supabaseClient.from('test').select('*')
|
||||
setData(data)
|
||||
}
|
||||
// Only run query once user is logged in.
|
||||
if (user) loadData()
|
||||
}, [user])
|
||||
|
||||
if (!user)
|
||||
return (
|
||||
<Auth
|
||||
redirectTo="http://localhost:3000/"
|
||||
appearance={{ theme: ThemeSupa }}
|
||||
supabaseClient={supabaseClient}
|
||||
providers={['google', 'github']}
|
||||
socialLayout="horizontal"
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => supabaseClient.auth.signOut()}>Sign out</button>
|
||||
<p>user:</p>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
<p>client-side data fetching with RLS</p>
|
||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginPage
|
||||
```
|
||||
|
||||
## Server-side rendering (SSR)
|
||||
|
||||
Create a server Supabase client to retrieve the logged in user's session:
|
||||
|
||||
```jsx pages/profile.js
|
||||
import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default function Profile({ user }) {
|
||||
return <div>Hello {user.name}</div>
|
||||
}
|
||||
|
||||
export const getServerSideProps = async (ctx) => {
|
||||
// Create authenticated Supabase Client
|
||||
const supabase = createPagesServerClient(ctx)
|
||||
// Check if we have a user
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser()
|
||||
|
||||
if (!user)
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/',
|
||||
permanent: false,
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
user,
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Server-side data fetching with RLS
|
||||
|
||||
You can use the server Supabase client to run [row level security](/docs/learn/auth-deep-dive/auth-row-level-security) authenticated queries server-side:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
queryGroup="language"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```jsx
|
||||
import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default function ProtectedPage({ user, data }) {
|
||||
return (
|
||||
<>
|
||||
<div>Protected content for {user.email}</div>
|
||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const getServerSideProps = async (ctx) => {
|
||||
// Create authenticated Supabase Client
|
||||
const supabase = createPagesServerClient(ctx)
|
||||
// Check if we have a session
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser()
|
||||
|
||||
if (!session)
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/',
|
||||
permanent: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Run queries with RLS on the server
|
||||
const { data } = await supabase.from('users').select('*')
|
||||
|
||||
return {
|
||||
props: {
|
||||
user,
|
||||
data: data ?? [],
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
```tsx
|
||||
import { User, createPagesServerClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { GetServerSidePropsContext } from 'next'
|
||||
|
||||
export default function ProtectedPage({ user, data }: { user: User; data: any }) {
|
||||
return (
|
||||
<>
|
||||
<div>Protected content for {user.email}</div>
|
||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||
// Create authenticated Supabase Client
|
||||
const supabase = createPagesServerClient(ctx)
|
||||
// Check if we have a session
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser()
|
||||
|
||||
if (!user)
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/',
|
||||
permanent: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Run queries with RLS on the server
|
||||
const { data } = await supabase.from('users').select('*')
|
||||
|
||||
return {
|
||||
props: {
|
||||
user,
|
||||
data: data ?? [],
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Server-side data fetching to OAuth APIs using `provider token` {`#oauth-provider-token`}
|
||||
|
||||
When using third-party auth providers, sessions are initiated with an additional `provider_token` field which is persisted in the auth cookie and can be accessed within the session object. The `provider_token` can be used to make API requests to the OAuth provider's API endpoints on behalf of the logged-in user.
|
||||
|
||||
Note that the server accesses data on the session object returned by `auth.getSession`. This data should normally not be trusted, because it is read from the local storage medium. It is not revalidated against the Auth server unless the session is expired, which means the sender can tamper with it.
|
||||
|
||||
In this case, the third-party API will validate the `provider_token`, and a malicious actor is unable to forge one.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
queryGroup="language"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```jsx
|
||||
import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default function ProtectedPage({ user, allRepos }) {
|
||||
return (
|
||||
<>
|
||||
<div>Protected content for {user.email}</div>
|
||||
<p>Data fetched with provider token:</p>
|
||||
<pre>{JSON.stringify(allRepos, null, 2)}</pre>
|
||||
<p>user:</p>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const getServerSideProps = async (ctx) => {
|
||||
// Create authenticated Supabase Client
|
||||
const supabase = createPagesServerClient(ctx)
|
||||
// Check if we have a session
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
if (!session)
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/',
|
||||
permanent: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Retrieve provider_token & logged in user's third-party id from metadata
|
||||
const { provider_token, user } = session
|
||||
const userId = user.user_metadata.user_name
|
||||
|
||||
const allRepos = await (
|
||||
await fetch(`https://api.github.com/search/repositories?q=user:${userId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `token ${provider_token}`,
|
||||
},
|
||||
})
|
||||
).json()
|
||||
|
||||
return { props: { user, allRepos } }
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
```tsx
|
||||
import { User, createPagesServerClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { GetServerSidePropsContext } from 'next'
|
||||
|
||||
export default function ProtectedPage({ user, allRepos }: { user: User; allRepos: any }) {
|
||||
return (
|
||||
<>
|
||||
<div>Protected content for {user.email}</div>
|
||||
<p>Data fetched with provider token:</p>
|
||||
<pre>{JSON.stringify(allRepos, null, 2)}</pre>
|
||||
<p>user:</p>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||
// Create authenticated Supabase Client
|
||||
const supabase = createPagesServerClient(ctx)
|
||||
// Check if we have a session
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
if (!session)
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/',
|
||||
permanent: false,
|
||||
},
|
||||
}
|
||||
|
||||
// Retrieve provider_token & logged in user's third-party id from metadata
|
||||
const { provider_token, user } = session
|
||||
const userId = user.user_metadata.user_name
|
||||
|
||||
const allRepos = await (
|
||||
await fetch(`https://api.github.com/search/repositories?q=user:${userId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `token ${provider_token}`,
|
||||
},
|
||||
})
|
||||
).json()
|
||||
|
||||
return { props: { user, allRepos } }
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Protecting API routes
|
||||
|
||||
Create a server Supabase client to retrieve the logged in user's session:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
queryGroup="language"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```jsx pages/api/protected-route.js
|
||||
import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
const ProtectedRoute = async (req, res) => {
|
||||
// Create authenticated Supabase Client
|
||||
const supabase = createPagesServerClient({ req, res })
|
||||
// Check if we have a user
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser()
|
||||
|
||||
if (!user)
|
||||
return res.status(401).json({
|
||||
error: 'not_authenticated',
|
||||
description: 'The user does not have an active session or is not authenticated',
|
||||
})
|
||||
|
||||
// Run queries with RLS on the server
|
||||
const { data } = await supabase.from('test').select('*')
|
||||
res.json(data)
|
||||
}
|
||||
|
||||
export default ProtectedRoute
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
```tsx pages/api/protected-route.ts
|
||||
import { NextApiHandler } from 'next'
|
||||
import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
const ProtectedRoute: NextApiHandler = async (req, res) => {
|
||||
// Create authenticated Supabase Client
|
||||
const supabase = createPagesServerClient({ req, res })
|
||||
// Check if we have a session
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser()
|
||||
|
||||
if (!user)
|
||||
return res.status(401).json({
|
||||
error: 'not_authenticated',
|
||||
description: 'The user does not have an active session or is not authenticated',
|
||||
})
|
||||
|
||||
// Run queries with RLS on the server
|
||||
const { data } = await supabase.from('test').select('*')
|
||||
res.json(data)
|
||||
}
|
||||
|
||||
export default ProtectedRoute
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Auth with Next.js proxy
|
||||
|
||||
As an alternative to protecting individual pages you can use a [Next.js Proxy](https://nextjs.org/docs/app/getting-started/proxy) to protect the entire directory or those that match the config object. In the following example, all requests to `/middleware-protected/*` will check whether a user is signed in, if successful the request will be forwarded to the destination route, otherwise the user will be redirected:
|
||||
|
||||
```ts middleware.ts
|
||||
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { NextResponse } from 'next/server'
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
export async function middleware(req: NextRequest) {
|
||||
// We need to create a response and hand it to the supabase client to be able to modify the response headers.
|
||||
const res = NextResponse.next()
|
||||
// Create authenticated Supabase Client.
|
||||
const supabase = createMiddlewareClient({ req, res })
|
||||
// Check if we have a session
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser()
|
||||
|
||||
// Check auth condition
|
||||
if (user?.email?.endsWith('@gmail.com')) {
|
||||
// Authentication successful, forward request to protected route.
|
||||
return res
|
||||
}
|
||||
|
||||
// Auth condition not met, redirect to home page.
|
||||
const redirectUrl = req.nextUrl.clone()
|
||||
redirectUrl.pathname = '/'
|
||||
redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname)
|
||||
return NextResponse.redirect(redirectUrl)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: '/middleware-protected/:path*',
|
||||
}
|
||||
```
|
||||
|
||||
## Migration guide
|
||||
|
||||
### Migrating to v0.7.X
|
||||
|
||||
#### PKCE Auth flow
|
||||
|
||||
PKCE is the new server-side auth flow implemented by the Next.js Auth Helpers. It requires a new API route for `/api/auth/callback` that exchanges an auth `code` for the user's `session`.
|
||||
|
||||
Check the [Code Exchange API Route steps](/docs/guides/auth/auth-helpers/nextjs-pages#code-exchange-api-route) above to implement this route.
|
||||
|
||||
#### Authentication
|
||||
|
||||
For authentication methods that have a `redirectTo` or `emailRedirectTo`, this must be set to this new code exchange API Route - `/api/auth/callback`. This is an example with the `signUp` function:
|
||||
|
||||
```jsx
|
||||
supabase.auth.signUp({
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'sup3rs3cur3',
|
||||
options: {
|
||||
emailRedirectTo: 'http://localhost:3000/auth/callback',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
#### Deprecated functions
|
||||
|
||||
With v0.7.x of the Next.js Auth Helpers a new naming convention has been implemented for `createClient` functions. The `createBrowserSupabaseClient` and `createServerSupabaseClient` functions have been marked as deprecated, and will be removed in a future version of the Auth Helpers.
|
||||
|
||||
- `createBrowserSupabaseClient` has been replaced with `createPagesBrowserClient`
|
||||
- `createServerSupabaseClient` has been replaced with `createPagesServerClient`
|
||||
|
||||
### Migrating to v0.5.X
|
||||
|
||||
To make these helpers more flexible as well as more maintainable and easier to upgrade for new versions of Next.js, we're stripping them down to the most useful part which is managing the cookies and giving you an authenticated supabase-js client in any environment (client, server, middleware/edge).
|
||||
|
||||
Therefore we're marking the `withApiAuth`, `withPageAuth`, and `withMiddlewareAuth` higher order functions as deprecated and they will be removed in the next **minor** release (v0.6.X).
|
||||
|
||||
Follow the steps below to update your API routes, pages, and middleware handlers. Thanks!
|
||||
|
||||
#### `withApiAuth` deprecated!
|
||||
|
||||
Use `createPagesServerClient` within your `NextApiHandler`:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="before"
|
||||
queryGroup="migration-side"
|
||||
>
|
||||
<TabPanel id="before" label="Before">
|
||||
|
||||
```tsx pages/api/protected-route.ts
|
||||
import { withApiAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default withApiAuth(async function ProtectedRoute(req, res, supabase) {
|
||||
// Run queries with RLS on the server
|
||||
const { data } = await supabase.from('test').select('*')
|
||||
res.json(data)
|
||||
})
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="after" label="After">
|
||||
|
||||
```tsx pages/api/protected-route.ts
|
||||
import { NextApiHandler } from 'next'
|
||||
import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
const ProtectedRoute: NextApiHandler = async (req, res) => {
|
||||
// Create authenticated Supabase Client
|
||||
const supabase = createPagesServerClient({ req, res })
|
||||
// Check if we have a session
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser()
|
||||
|
||||
if (!user)
|
||||
return res.status(401).json({
|
||||
error: 'not_authenticated',
|
||||
description: 'The user does not have an active session or is not authenticated',
|
||||
})
|
||||
|
||||
// Run queries with RLS on the server
|
||||
const { data } = await supabase.from('test').select('*')
|
||||
res.json(data)
|
||||
}
|
||||
|
||||
export default ProtectedRoute
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
#### `withPageAuth` deprecated!
|
||||
|
||||
Use `createPagesServerClient` within `getServerSideProps`:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="before"
|
||||
queryGroup="migration-side"
|
||||
>
|
||||
<TabPanel id="before" label="Before">
|
||||
|
||||
```tsx pages/profile.tsx
|
||||
import { withPageAuth, User } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default function Profile({ user }: { user: User }) {
|
||||
return <pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
}
|
||||
|
||||
export const getServerSideProps = withPageAuth({ redirectTo: '/' })
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="after" label="After">
|
||||
|
||||
```tsx pages/profile.js
|
||||
import { createPagesServerClient, User } from '@supabase/auth-helpers-nextjs'
|
||||
import { GetServerSidePropsContext } from 'next'
|
||||
|
||||
export default function Profile({ user }: { user: User }) {
|
||||
return <pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
}
|
||||
|
||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||
// Create authenticated Supabase Client
|
||||
const supabase = createPagesServerClient(ctx)
|
||||
// Check if we have a session
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser()
|
||||
|
||||
if (!user)
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/',
|
||||
permanent: false,
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
initialSession: session,
|
||||
user: session.user,
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
#### `withMiddlewareAuth` deprecated!
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="before"
|
||||
queryGroup="migration-side"
|
||||
>
|
||||
<TabPanel id="before" label="Before">
|
||||
|
||||
```tsx middleware.ts
|
||||
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export const middleware = withMiddlewareAuth({
|
||||
redirectTo: '/',
|
||||
authGuard: {
|
||||
isPermitted: async (user) => {
|
||||
return user.email?.endsWith('@gmail.com') ?? false
|
||||
},
|
||||
redirectTo: '/insufficient-permissions',
|
||||
},
|
||||
})
|
||||
|
||||
export const config = {
|
||||
matcher: '/middleware-protected',
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="after" label="After">
|
||||
|
||||
```tsx middleware.ts
|
||||
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { NextResponse } from 'next/server'
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
export async function middleware(req: NextRequest) {
|
||||
// We need to create a response and hand it to the supabase client to be able to modify the response headers.
|
||||
const res = NextResponse.next()
|
||||
// Create authenticated Supabase Client.
|
||||
const supabase = createMiddlewareClient({ req, res })
|
||||
// Check if we have a session
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser()
|
||||
|
||||
// Check auth condition
|
||||
if (user?.email?.endsWith('@gmail.com')) {
|
||||
// Authentication successful, forward request to protected route.
|
||||
return res
|
||||
}
|
||||
|
||||
// Auth condition not met, redirect to home page.
|
||||
const redirectUrl = req.nextUrl.clone()
|
||||
redirectUrl.pathname = '/'
|
||||
redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname)
|
||||
return NextResponse.redirect(redirectUrl)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: '/middleware-protected',
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Migrating to v0.4.X and supabase-js v2
|
||||
|
||||
With the update to `supabase-js` v2 the `auth` API routes are no longer required, therefore you can go ahead and delete your `auth` directory under the `/pages/api/` directory. Refer to the [v2 migration guide](/docs/reference/javascript/v1/upgrade-guide) for the full set of changes within supabase-js.
|
||||
|
||||
The `/api/auth/logout` API route has been removed, use the `signout` method instead:
|
||||
|
||||
```jsx
|
||||
<button
|
||||
onClick={async () => {
|
||||
await supabaseClient.auth.signOut()
|
||||
router.push('/')
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
```
|
||||
|
||||
The `supabaseClient` and `supabaseServerClient` have been removed in favor of the `createPagesBrowserClient` and `createPagesServerClient` methods. This allows you to provide the CLI-generated types to the client:
|
||||
|
||||
```tsx
|
||||
// client-side
|
||||
import type { Database } from 'types_db'
|
||||
const [supabaseClient] = useState(() => createPagesBrowserClient<Database>())
|
||||
|
||||
// server-side API route
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import type { Database } from 'types_db'
|
||||
|
||||
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const supabaseServerClient = createPagesServerClient<Database>({
|
||||
req,
|
||||
res,
|
||||
})
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabaseServerClient.auth.getUser()
|
||||
|
||||
res.status(200).json({ name: user?.name ?? '' })
|
||||
}
|
||||
```
|
||||
|
||||
- The `UserProvider` has been replaced by the `SessionContextProvider`. Make sure to wrap your `pages/_app.js` component with the `SessionContextProvider`. Then, throughout your application you can use the `useSessionContext` hook to get the `session` and the `useSupabaseClient` hook to get an authenticated `supabaseClient`.
|
||||
- The `useUser` hook now returns the `user` object or `null`.
|
||||
- Usage with TypeScript: You can pass types that were [generated with the Supabase CLI](/docs/reference/javascript/typescript-support#generating-types) to the Supabase Client to get enhanced type safety and auto completion:
|
||||
|
||||
Creating a new `supabase` client object:
|
||||
|
||||
```tsx
|
||||
import { Database } from '../database.types'
|
||||
|
||||
const [supabaseClient] = useState(() => createPagesBrowserClient<Database>())
|
||||
```
|
||||
|
||||
Retrieving a `supabase` client object from the `SessionContext`:
|
||||
|
||||
```tsx
|
||||
import { useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
import { Database } from '../database.types'
|
||||
|
||||
const supabaseClient = useSupabaseClient<Database>()
|
||||
```
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</Accordion>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,855 +0,0 @@
|
||||
---
|
||||
id: 'remix'
|
||||
title: 'Supabase Auth with Remix'
|
||||
description: 'Authentication helpers for loaders and actions in Remix.'
|
||||
sidebar_label: 'Remix'
|
||||
sitemapPriority: 0.3
|
||||
---
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
The Auth helpers package is deprecated. Use the new `@supabase/ssr` package for Server Side Authentication. `@supabase/ssr` takes the core concepts of the Auth Helpers package and makes them available to any server framework. Read the [migration doc](/docs/guides/auth/server-side/migrating-to-ssr-from-auth-helpers) to learn more.
|
||||
|
||||
</Admonition>
|
||||
|
||||
<Accordion
|
||||
type="default"
|
||||
openBehaviour="multiple"
|
||||
chevronAlign="right"
|
||||
justified
|
||||
size="medium"
|
||||
className="text-foreground-light border-b mt-8 pb-2"
|
||||
>
|
||||
|
||||
<AccordionItem
|
||||
header="See legacy docs"
|
||||
id="legacy-docs"
|
||||
>
|
||||
|
||||
This submodule provides convenience helpers for implementing user authentication in Remix applications.
|
||||
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/Viaed7XWCY8"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
<Admonition type="tip">
|
||||
|
||||
For a complete implementation example, check out [this free egghead course](https://egghead.io/courses/build-a-realtime-chat-app-with-remix-and-supabase-d36e2618) or [this GitHub repo](https://github.com/supabase/auth-helpers/tree/main/examples/remix).
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Install the Remix helper library
|
||||
|
||||
```sh Terminal
|
||||
npm install @supabase/auth-helpers-remix @supabase/supabase-js
|
||||
```
|
||||
|
||||
This library supports the following tooling versions:
|
||||
|
||||
- Remix: `>=1.7.2`
|
||||
|
||||
## Set up environment variables
|
||||
|
||||
Retrieve your project URL and anon key in your project's [API settings](/dashboard/project/_/settings/api) in the Dashboard to set up the following environment variables. For local development you can set them in a `.env` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/remix/.env.example).
|
||||
|
||||
```bash .env
|
||||
SUPABASE_URL=YOUR_SUPABASE_URL
|
||||
SUPABASE_PUBLISHABLE_KEY=YOUR_SUPABASE_PUBLISHABLE_KEY
|
||||
```
|
||||
|
||||
### Code Exchange route
|
||||
|
||||
The `Code Exchange` route is required for the [server-side auth flow](/docs/guides/auth/server-side-rendering) implemented by the Remix Auth Helpers. It exchanges an auth `code` for the user's `session`, which is set as a cookie for future requests made to Supabase.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
queryGroup="language"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
Create a new file at `app/routes/auth.callback.jsx` and populate with the following:
|
||||
|
||||
```jsx app/routes/auth.callback.jsx
|
||||
import { redirect } from '@remix-run/node'
|
||||
import { createServerClient } from '@supabase/auth-helpers-remix'
|
||||
|
||||
export const loader = async ({ request }) => {
|
||||
const response = new Response()
|
||||
const url = new URL(request.url)
|
||||
const code = url.searchParams.get('code')
|
||||
|
||||
if (code) {
|
||||
const supabaseClient = createServerClient(
|
||||
process.env.SUPABASE_URL,
|
||||
process.env.SUPABASE_PUBLISHABLE_KEY,
|
||||
{ request, response }
|
||||
)
|
||||
await supabaseClient.auth.exchangeCodeForSession(code)
|
||||
}
|
||||
|
||||
return redirect('/', {
|
||||
headers: response.headers,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
Create a new file at `app/routes/auth.callback.tsx` and populate with the following:
|
||||
|
||||
```tsx app/routes/auth.callback.tsx
|
||||
import { redirect } from '@remix-run/node'
|
||||
import { createServerClient } from '@supabase/auth-helpers-remix'
|
||||
|
||||
import type { Database } from 'db_types'
|
||||
import type { LoaderFunctionArgs } from '@remix-run/node'
|
||||
|
||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
const response = new Response()
|
||||
const url = new URL(request.url)
|
||||
const code = url.searchParams.get('code')
|
||||
|
||||
if (code) {
|
||||
const supabaseClient = createServerClient<Database>(
|
||||
process.env.SUPABASE_URL!,
|
||||
process.env.SUPABASE_PUBLISHABLE_KEY!,
|
||||
{ request, response }
|
||||
)
|
||||
await supabaseClient.auth.exchangeCodeForSession(code)
|
||||
}
|
||||
|
||||
return redirect('/', {
|
||||
headers: response.headers,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
> `Database` is a TypeScript definitions file [generated by the Supabase CLI](/docs/reference/javascript/typescript-support#generating-types).
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Server-side
|
||||
|
||||
The Supabase client can now be used server-side - in loaders and actions - by calling the `createServerClient` function.
|
||||
|
||||
### Loader
|
||||
|
||||
Loader functions run on the server immediately before the component is rendered. They respond to all GET requests on a route. You can create an authenticated Supabase client by calling the `createServerClient` function and passing it your `SUPABASE_URL`, `SUPABASE_PUBLISHABLE_KEY`, and a `Request` and `Response`.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
queryGroup="language"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```jsx
|
||||
import { json } from '@remix-run/node' // change this import to whatever runtime you are using
|
||||
import { createServerClient } from '@supabase/auth-helpers-remix'
|
||||
|
||||
export const loader = async ({ request }) => {
|
||||
const response = new Response()
|
||||
// an empty response is required for the auth helpers
|
||||
// to set cookies to manage auth
|
||||
|
||||
const supabaseClient = createServerClient(
|
||||
process.env.SUPABASE_URL,
|
||||
process.env.SUPABASE_PUBLISHABLE_KEY,
|
||||
{ request, response }
|
||||
)
|
||||
|
||||
const { data } = await supabaseClient.from('test').select('*')
|
||||
|
||||
// in order for the set-cookie header to be set,
|
||||
// headers must be returned as part of the loader response
|
||||
return json(
|
||||
{ data },
|
||||
{
|
||||
headers: response.headers,
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<Admonition type="tip">
|
||||
|
||||
Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function.
|
||||
|
||||
</Admonition>
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
```jsx
|
||||
import { json } from '@remix-run/node' // change this import to whatever runtime you are using
|
||||
import { createServerClient } from '@supabase/auth-helpers-remix'
|
||||
|
||||
import type { LoaderFunctionArgs } from '@remix-run/node' // change this import to whatever runtime you are using
|
||||
|
||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
const response = new Response()
|
||||
const supabaseClient = createServerClient(
|
||||
process.env.SUPABASE_URL!,
|
||||
process.env.SUPABASE_PUBLISHABLE_KEY!,
|
||||
{ request, response }
|
||||
)
|
||||
|
||||
const { data } = await supabaseClient.from('test').select('*')
|
||||
|
||||
return json(
|
||||
{ data },
|
||||
{
|
||||
headers: response.headers,
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<Admonition type="tip">
|
||||
|
||||
Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function.
|
||||
|
||||
</Admonition>
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Action
|
||||
|
||||
Action functions run on the server and respond to HTTP requests to a route, other than GET - POST, PUT, PATCH, DELETE etc. You can create an authenticated Supabase client by calling the `createServerClient` function and passing it your `SUPABASE_URL`, `SUPABASE_PUBLISHABLE_KEY`, and a `Request` and `Response`.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
queryGroup="language"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```jsx
|
||||
import { json } from '@remix-run/node' // change this import to whatever runtime you are using
|
||||
import { createServerClient } from '@supabase/auth-helpers-remix'
|
||||
|
||||
export const action = async ({ request }) => {
|
||||
const response = new Response()
|
||||
|
||||
const supabaseClient = createServerClient(
|
||||
process.env.SUPABASE_URL,
|
||||
process.env.SUPABASE_PUBLISHABLE_KEY,
|
||||
{ request, response }
|
||||
)
|
||||
|
||||
const { data } = await supabaseClient.from('test').select('*')
|
||||
|
||||
return json(
|
||||
{ data },
|
||||
{
|
||||
headers: response.headers,
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<Admonition type="tip">
|
||||
|
||||
Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function.
|
||||
|
||||
</Admonition>
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
```jsx
|
||||
import { json } from '@remix-run/node' // change this import to whatever runtime you are using
|
||||
import { createServerClient } from '@supabase/auth-helpers-remix'
|
||||
|
||||
import type { ActionFunctionArgs } from '@remix-run/node' // change this import to whatever runtime you are using
|
||||
|
||||
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
const response = new Response()
|
||||
|
||||
const supabaseClient = createServerClient(
|
||||
process.env.SUPABASE_URL!,
|
||||
process.env.SUPABASE_PUBLISHABLE_KEY!,
|
||||
{ request, response }
|
||||
)
|
||||
|
||||
const { data } = await supabaseClient.from('test').select('*')
|
||||
|
||||
return json(
|
||||
{ data },
|
||||
{
|
||||
headers: response.headers,
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<Admonition type="tip">
|
||||
|
||||
Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function.
|
||||
|
||||
</Admonition>
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Session and user
|
||||
|
||||
You can determine if a user is authenticated by checking their session using the `getSession` function.
|
||||
|
||||
```jsx
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabaseClient.auth.getSession()
|
||||
```
|
||||
|
||||
The session contains a user property. This is the user metadata saved, unencoded, to the local storage medium. It's unverified and can be tampered by the user, so don't use it for authorization or sensitive purposes.
|
||||
|
||||
<$Partial path="get_session_warning.mdx" />
|
||||
|
||||
```jsx
|
||||
const user = session?.user
|
||||
```
|
||||
|
||||
Or, if you need trusted user data, you can call the `getUser()` function, which retrieves the trusted user data by making a request to the Supabase Auth server.
|
||||
|
||||
```jsx
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabaseClient.auth.getUser()
|
||||
```
|
||||
|
||||
## Client-side
|
||||
|
||||
We still need to use Supabase client-side for things like authentication and realtime subscriptions. Anytime we use Supabase client-side it needs to be a single instance.
|
||||
|
||||
### Creating a singleton Supabase client
|
||||
|
||||
Since our environment variables are not available client-side, we need to plumb them through from the loader.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
queryGroup="language"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```jsx app/root.jsx
|
||||
export const loader = () => {
|
||||
const env = {
|
||||
SUPABASE_URL: process.env.SUPABASE_URL,
|
||||
SUPABASE_PUBLISHABLE_KEY: process.env.SUPABASE_PUBLISHABLE_KEY,
|
||||
}
|
||||
|
||||
return json({ env })
|
||||
}
|
||||
```
|
||||
|
||||
<Admonition type="tip">
|
||||
|
||||
These may not be stored in `process.env` for environments other than Node.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Next, we call the `useLoaderData` hook in our component to get the `env` object.
|
||||
|
||||
```jsx app/root.jsx
|
||||
const { env } = useLoaderData()
|
||||
```
|
||||
|
||||
We then want to instantiate a single instance of a Supabase browser client, to be used across our client-side components.
|
||||
|
||||
```jsx app/root.jsx
|
||||
const [supabase] = useState(() =>
|
||||
createBrowserClient(env.SUPABASE_URL, env.SUPABASE_PUBLISHABLE_KEY)
|
||||
)
|
||||
```
|
||||
|
||||
And then we can share this instance across our application with Outlet Context.
|
||||
|
||||
```jsx app/root.jsx
|
||||
<Outlet context={{ supabase }} />
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
```tsx app/root.tsx
|
||||
export const loader = ({}: LoaderFunctionArgs) => {
|
||||
const env = {
|
||||
SUPABASE_URL: process.env.SUPABASE_URL!,
|
||||
SUPABASE_PUBLISHABLE_KEY: process.env.SUPABASE_PUBLISHABLE_KEY!,
|
||||
}
|
||||
|
||||
return json({ env })
|
||||
}
|
||||
```
|
||||
|
||||
<Admonition type="tip">
|
||||
|
||||
These may not be stored in `process.env` for environments other than Node.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Next, we call the `useLoaderData` hook in our component to get the `env` object.
|
||||
|
||||
```tsx app/root.tsx
|
||||
const { env } = useLoaderData<typeof loader>()
|
||||
```
|
||||
|
||||
We then want to instantiate a single instance of a Supabase browser client, to be used across our client-side components.
|
||||
|
||||
```tsx app/root.tsx
|
||||
const [supabase] = useState(() =>
|
||||
createBrowserClient<Database>(env.SUPABASE_URL, env.SUPABASE_PUBLISHABLE_KEY)
|
||||
)
|
||||
```
|
||||
|
||||
And then we can share this instance across our application with Outlet Context.
|
||||
|
||||
```tsx app/root.tsx
|
||||
<Outlet context={{ supabase }} />
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Syncing server and client state
|
||||
|
||||
Since authentication happens client-side, we need to tell Remix to re-call all active loaders when the user signs in or out.
|
||||
|
||||
Remix provides a hook `useRevalidator` that can be used to revalidate all loaders on the current route.
|
||||
|
||||
Now to determine when to submit a post request to this action, we need to compare the server and client state for the user's access token.
|
||||
|
||||
Let's pipe that through from our loader.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
queryGroup="language"
|
||||
>
|
||||
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```jsx app/root.jsx
|
||||
export const loader = async ({ request }) => {
|
||||
const env = {
|
||||
SUPABASE_URL: process.env.SUPABASE_URL,
|
||||
SUPABASE_PUBLISHABLE_KEY: process.env.SUPABASE_PUBLISHABLE_KEY,
|
||||
}
|
||||
|
||||
const response = new Response()
|
||||
|
||||
const supabase = createServerClient(
|
||||
process.env.SUPABASE_URL,
|
||||
process.env.SUPABASE_PUBLISHABLE_KEY,
|
||||
{
|
||||
request,
|
||||
response,
|
||||
}
|
||||
)
|
||||
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
return json(
|
||||
{
|
||||
env,
|
||||
session,
|
||||
},
|
||||
{
|
||||
headers: response.headers,
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
```tsx app/root.tsx
|
||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
const env = {
|
||||
SUPABASE_URL: process.env.SUPABASE_URL!,
|
||||
SUPABASE_PUBLISHABLE_KEY: process.env.SUPABASE_PUBLISHABLE_KEY!,
|
||||
}
|
||||
|
||||
const response = new Response()
|
||||
|
||||
const supabase = createServerClient(
|
||||
process.env.SUPABASE_URL!,
|
||||
process.env.SUPABASE_PUBLISHABLE_KEY!,
|
||||
{
|
||||
request,
|
||||
response,
|
||||
}
|
||||
)
|
||||
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
return json(
|
||||
{
|
||||
env,
|
||||
session,
|
||||
},
|
||||
{
|
||||
headers: response.headers,
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
</Tabs>
|
||||
|
||||
And then use the revalidator, inside the `onAuthStateChange` hook.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
queryGroup="language"
|
||||
>
|
||||
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```jsx app/root.jsx
|
||||
const { env, session } = useLoaderData()
|
||||
const { revalidate } = useRevalidator()
|
||||
|
||||
const [supabase] = useState(() =>
|
||||
createBrowserClient(env.SUPABASE_URL, env.SUPABASE_PUBLISHABLE_KEY)
|
||||
)
|
||||
|
||||
const serverAccessToken = session?.access_token
|
||||
|
||||
useEffect(() => {
|
||||
const {
|
||||
data: { subscription },
|
||||
} = supabase.auth.onAuthStateChange((event, session) => {
|
||||
if (session?.access_token !== serverAccessToken) {
|
||||
// server and client are out of sync.
|
||||
revalidate()
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
subscription.unsubscribe()
|
||||
}
|
||||
}, [serverAccessToken, supabase, revalidate])
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
```tsx app/root.tsx
|
||||
const { env, session } = useLoaderData<typeof loader>()
|
||||
const { revalidate } = useRevalidator()
|
||||
|
||||
const [supabase] = useState(() =>
|
||||
createBrowserClient<Database>(env.SUPABASE_URL, env.SUPABASE_PUBLISHABLE_KEY)
|
||||
)
|
||||
|
||||
const serverAccessToken = session?.access_token
|
||||
|
||||
useEffect(() => {
|
||||
const {
|
||||
data: { subscription },
|
||||
} = supabase.auth.onAuthStateChange((event, session) => {
|
||||
if (event !== 'INITIAL_SESSION' && session?.access_token !== serverAccessToken) {
|
||||
// server and client are out of sync.
|
||||
revalidate()
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
subscription.unsubscribe()
|
||||
}
|
||||
}, [serverAccessToken, supabase, revalidate])
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
</Tabs>
|
||||
|
||||
<Admonition type="tip">
|
||||
|
||||
Check out [this repo](https://github.com/supabase/auth-helpers/tree/main/examples/remix) for full implementation example
|
||||
|
||||
</Admonition>
|
||||
|
||||
### Authentication
|
||||
|
||||
Now we can use our outlet context to access our single instance of Supabase and use any of the [supported authentication strategies from `supabase-js`](/docs/reference/javascript/auth-signup).
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
queryGroup="language"
|
||||
>
|
||||
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```jsx app/components/login.jsx
|
||||
export default function Login() {
|
||||
const { supabase } = useOutletContext()
|
||||
|
||||
const handleEmailLogin = async () => {
|
||||
await supabase.auth.signInWithPassword({
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'password',
|
||||
})
|
||||
}
|
||||
|
||||
const handleGitHubLogin = async () => {
|
||||
await supabase.auth.signInWithOAuth({
|
||||
provider: 'github',
|
||||
options: {
|
||||
redirectTo: 'http://localhost:3000/auth/callback',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
await supabase.auth.signOut()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={handleEmailLogin}>Email Login</button>
|
||||
<button onClick={handleGitHubLogin}>GitHub Login</button>
|
||||
<button onClick={handleLogout}>Logout</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
```tsx app/components/login.tsx
|
||||
export default function Login() {
|
||||
const { supabase } = useOutletContext<{ supabase: SupabaseClient<Database> }>()
|
||||
|
||||
const handleEmailLogin = async () => {
|
||||
await supabase.auth.signInWithPassword({
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'password',
|
||||
})
|
||||
}
|
||||
|
||||
const handleGitHubLogin = async () => {
|
||||
await supabase.auth.signInWithOAuth({
|
||||
provider: 'github',
|
||||
options: {
|
||||
redirectTo: 'http://localhost:3000/auth/callback',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
await supabase.auth.signOut()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={handleEmailLogin}>Email Login</button>
|
||||
<button onClick={handleGitHubLogin}>GitHub Login</button>
|
||||
<button onClick={handleLogout}>Logout</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
</Tabs>
|
||||
|
||||
### Subscribe to realtime events
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
queryGroup="language"
|
||||
>
|
||||
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```jsx app/routes/realtime.jsx
|
||||
import { useLoaderData, useOutletContext } from '@remix-run/react'
|
||||
import { createServerClient } from '@supabase/auth-helpers-remix'
|
||||
import { json } from '@remix-run/node'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
export const loader = async ({ request }) => {
|
||||
const response = new Response()
|
||||
const supabase = createServerClient(
|
||||
process.env.SUPABASE_URL,
|
||||
process.env.SUPABASE_PUBLISHABLE_KEY,
|
||||
{
|
||||
request,
|
||||
response,
|
||||
}
|
||||
)
|
||||
|
||||
const { data } = await supabase.from('posts').select()
|
||||
|
||||
return json({ serverPosts: data ?? [] }, { headers: response.headers })
|
||||
}
|
||||
|
||||
export default function Index() {
|
||||
const { serverPosts } = useLoaderData()
|
||||
const [posts, setPosts] = useState(serverPosts)
|
||||
const { supabase } = useOutletContext()
|
||||
|
||||
useEffect(() => {
|
||||
setPosts(serverPosts)
|
||||
}, [serverPosts])
|
||||
|
||||
useEffect(() => {
|
||||
const channel = supabase
|
||||
.channel('*')
|
||||
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'posts' }, (payload) =>
|
||||
setPosts([...posts, payload.new])
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
return () => {
|
||||
supabase.removeChannel(channel)
|
||||
}
|
||||
}, [supabase, posts, setPosts])
|
||||
|
||||
return <pre>{JSON.stringify(posts, null, 2)}</pre>
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
```tsx app/routes/realtime.tsx
|
||||
import { useLoaderData, useOutletContext } from '@remix-run/react'
|
||||
import { createServerClient } from '@supabase/auth-helpers-remix'
|
||||
import { json } from '@remix-run/node'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import type { SupabaseClient } from '@supabase/auth-helpers-remix'
|
||||
import type { Database } from 'db_types'
|
||||
|
||||
type Post = Database['public']['Tables']['posts']['Row']
|
||||
|
||||
import type { LoaderFunctionArgs } from '@remix-run/node'
|
||||
|
||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
const response = new Response()
|
||||
const supabase = createServerClient<Database>(
|
||||
process.env.SUPABASE_URL!,
|
||||
process.env.SUPABASE_PUBLISHABLE_KEY!,
|
||||
{
|
||||
request,
|
||||
response,
|
||||
}
|
||||
)
|
||||
|
||||
const { data } = await supabase.from('posts').select()
|
||||
|
||||
return json({ serverPosts: data ?? [] }, { headers: response.headers })
|
||||
}
|
||||
|
||||
export default function Index() {
|
||||
const { serverPosts } = useLoaderData<typeof loader>()
|
||||
const [posts, setPosts] = useState(serverPosts)
|
||||
const { supabase } = useOutletContext<{ supabase: SupabaseClient<Database> }>()
|
||||
|
||||
useEffect(() => {
|
||||
setPosts(serverPosts)
|
||||
}, [serverPosts])
|
||||
|
||||
useEffect(() => {
|
||||
const channel = supabase
|
||||
.channel('*')
|
||||
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'posts' }, (payload) =>
|
||||
setPosts([...posts, payload.new as Post])
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
return () => {
|
||||
supabase.removeChannel(channel)
|
||||
}
|
||||
}, [supabase, posts, setPosts])
|
||||
|
||||
return <pre>{JSON.stringify(posts, null, 2)}</pre>
|
||||
}
|
||||
```
|
||||
|
||||
> `Database` is a TypeScript definitions file [generated by the Supabase CLI](/docs/reference/javascript/typescript-support#generating-types).
|
||||
|
||||
</TabPanel>
|
||||
|
||||
</Tabs>
|
||||
|
||||
<Admonition type="tip">
|
||||
|
||||
Ensure you have [enabled replication](/dashboard/project/_/database/publications) on the table you are subscribing to.
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Migration guide
|
||||
|
||||
### Migrating to v0.2.0
|
||||
|
||||
#### PKCE Auth flow
|
||||
|
||||
PKCE is the new server-side auth flow implemented by the Remix Auth Helpers. It requires a new `loader` route for `/auth/callback` that exchanges an auth `code` for the user's `session`.
|
||||
|
||||
Check the [Code Exchange Route steps](/docs/guides/auth/auth-helpers/remix#code-exchange-route) above to implement this route.
|
||||
|
||||
#### Authentication
|
||||
|
||||
For authentication methods that have a `redirectTo` or `emailRedirectTo`, this must be set to this new code exchange API Route - `/api/auth/callback`. This is an example with the `signUp` function:
|
||||
|
||||
```jsx
|
||||
supabaseClient.auth.signUp({
|
||||
email: 'valid.email@supabase.io',
|
||||
password: 'sup3rs3cur3',
|
||||
options: {
|
||||
emailRedirectTo: 'http://localhost:3000/auth/callback',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
</AccordionItem>
|
||||
|
||||
</Accordion>
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user