Merge pull request #9998 from supabase/master

Prod deploy dashboard
This commit is contained in:
Joshen Lim authored and GitHub committed 2022-11-02 11:53:35 +07:00
commit 1d92980f9a
491 files changed
+10607 -6456

No files matched your search

@@ -316,13 +316,13 @@ import { Session } from '@supabase/supabase-js'
setupIonicReact()
const App: React.FC = () => {
const [session, setSession] = (useState < Session) | (null > null)
const [session, setSession] = useState<Session>(null)
useEffect(() => {
setSession(supabase.auth.session())
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session)
})
}, [session])
}, [])
return (
<IonApp>
<IonReactRouter>
-37
View File
@@ -1,37 +0,0 @@
---
slug: /
sidebar_position: 1
sidebar_label: Supabase API
---
# Supabase API
The Supabase API allows you to manage your projects programmatically.
## Status
The Supabase API is in `beta`. It is usable in it's current state, but it's likely that there will be breaking changes.
## Authentication
All API requests require a Supabase Personal token to be included in the Authorization header: `Authorization Bearer <supabase_personal_token`.
To generate or manage your API token, visit your [account](https://app.supabase.com/account/tokens) page.
Your API tokens carry the same privileges as your user account, so be sure to keep it secret.
```bash
$ curl https://api.supabase.com/v1/projects \
-H "Authorization: Bearer sbp_bdd0••••••••••••••••••••••••••••••••4f23"
```
All API requests must be authenticated and made over HTTPS.
## Rate limits
The API is currently subject to our fair-use policy. In the future, are likely to introduce rate limits.
All resources created via the API are subject to the pricing detailed on our [Pricing](https://supabase.com/pricing) pages.
## Additional links
- [OpenAPI Docs](https://api.supabase.com/api/v1)
- [OpenAPI Spec](https://api.supabase.com/api/v1-json)
- Reporting bugs and issues: [github.com/supabase/supabase](https://github.com/supabase/supabase)
-12
View File
@@ -1,12 +0,0 @@
---
id: release-notes
---
# Release Notes
## v1.0.0 {#1.0.0}
Adds the following routes:
- `/v1/organizations`: Manage Supabase organizations
- `/v1/projects`: Manage Supabase projects
File diff suppressed because it is too large. Load diff
Whitespace-only changes.
Whitespace-only changes.
-1
View File
@@ -1 +0,0 @@
[]
-29
View File
@@ -1,29 +0,0 @@
---
id: intro
slug: /
sidebar_position: 1
sidebar_label: Supabase CLI
hide_table_of_contents: true
---
# Supabase CLI
The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.
The CLI is still under development, but it contains all the functionality for working with your Supabase projects and the Supabase Platform.
- Run Supabase locally: [`supabase start`](https://supabase.com/docs/reference/cli/usage#supabase-start)
- Manage database migrations: [`supabase migration`](https://supabase.com/docs/reference/cli/usage#supabase-migration)
- CI/CD for releasing to production: [`supabase db push`](https://supabase.com/docs/reference/cli/usage#supabase-db-push)
- Manage your Supabase projects: [`supabase projects`](https://supabase.com/docs/reference/cli/usage#supabase-projects)
- Generate types directly from your database schema: [`supabase gen types`](https://supabase.com/docs/reference/cli/usage#supabase-gen)
- A [community-supported GitHub Action](https://github.com/lyqht/generate-supabase-db-types-github-action) to generate TypeScript types
- Shell autocomplete: [`supabase completion`](https://supabase.com/docs/reference/cli/usage#supabase-completion)
- A [community-supported Fig autocomplete spec](https://fig.io/manual/supabase) for macOS terminal
## Additional Links
- [Install the Supabase CLI](/docs/guides/cli)
- [Source code](https://github.com/supabase/cli)
- [Known bugs and issues](https://github.com/supabase/cli/issues)
- [Supabase CLI v1 and Admin API Beta](https://supabase.com/blog/supabase-cli-v1-and-admin-api-beta)
- [The CLI team announcing CLI V1 and Admin API Beta - Video](https://www.youtube.com/watch?v=OpPOaJI_Z28)
-7
View File
@@ -1,7 +0,0 @@
---
id: release-notes
---
# Release Notes
All release notes can be found in the [GitHub Releases page](https://github.com/supabase/cli/releases).
Whitespace-only changes.
Whitespace-only changes.
-1
View File
@@ -1 +0,0 @@
[]
-35
View File
@@ -1,35 +0,0 @@
---
id: config
slug: /config
title: Configuration
toc_max_heading_level: 3
---
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY -->
A `config.toml` file is generated after running `supabase init`.
This file is located in the `supabase` folder under `supabase/config.toml`.
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY -->
## General {#general}
### `project_id` {#project_id}
A string used to distinguish different Supabase projects on the same host. Defaults to the working directory name when running `supabase init`.
<ul>
<li>Required: <code>true</code></li>
<li>Default: <code>None</code></li>
</ul>
<br />
-22
View File
@@ -1,22 +0,0 @@
---
slug: /
sidebar_position: 1
sidebar_label: Auth Server
---
# Supabase Auth Server
The Supabase Auth Server (GoTrue) is a JSON Web Token (JWT)-based API for managing users and issuing access tokens.
GoTrue is an open-source API written in Golang, that acts as a self-standing API service for handling user registration and authentication for JAM projects. It's based on OAuth2 and JWT and handles user signup, authentication, and custom user data.
## Client libraries
- [JavaScript](https://github.com/supabase/gotrue-js)
- [Dart](https://github.com/supabase/gotrue-dart)
## Additional Links
- [Source Code](https://github.com/supabase/gotrue)
- [Known bugs and issues](https://github.com/supabase/gotrue/issues)
- [Auth Guides](https://supabase.com/docs/guides/auth)
-7
View File
@@ -1,7 +0,0 @@
---
id: release-notes
---
# Release Notes
All release notes can be found in the [GitHub Releases](https://github.com/supabase/gotrue/releases) page.
-18
View File
@@ -1,18 +0,0 @@
---
id: usage
slug: /usage
title: Usage
toc_max_heading_level: 3
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
Documentation of the gotrue API.
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
Whitespace-only changes.
Whitespace-only changes.
-1
View File
@@ -1 +0,0 @@
[]
-28
View File
@@ -1,28 +0,0 @@
---
slug: /
sidebar_position: 1
sidebar_label: Storage Server
---
# Supabase Storage Server
An S3 compatible object storage service that integrates with Postgres.
- Uses Postgres as it's datastore for storing metadata
- Authorization rules are written as Postgres Row Level Security policies
- Integrates with S3 as the storage backend (with more in the pipeline!)
- Extremely lightweight and performant
Read [this post](https://supabase.com/blog/supabase-storage) on why we decided to build a new object storage service.
## Client libraries
- [JavaScript](https://github.com/supabase/storage-js)
- [Dart](https://github.com/supabase/storage-dart)
## Additional Links
- [Source Code](https://github.com/supabase/storage-api)
- [Known bugs and issues](https://github.com/supabase/storage-js/issues)
- [Storage Guides](https://supabase.com/docs/guides/storage)
- [OpenAPI Docs](https://supabase.github.io/storage-api/)
@@ -1,7 +0,0 @@
---
id: release-notes
---
# Release Notes
All release notes can be found in the [GitHub Releases](https://github.com/supabase/storage-api/releases) page.
Whitespace-only changes.
Whitespace-only changes.
-1
View File
@@ -1 +0,0 @@
[]
@@ -1,43 +0,0 @@
---
id: initializing
title: 'Initializing'
slug: initializing
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
## Flutter
For `supabase-flutter`, you will be using the static `initialize()` method on `Supabase` class.
### Flutter `initialize()`
```dart title="main.dart"
Future<void> main() async {
await Supabase.initialize(url: 'https://xyzcompany.supabase.co', anonKey: 'public-anon-key');
runApp(MyApp());
}
```
### Access `SupabaseClient` instance
Once you initialize Supabase in your `main()` method, you can access the `SupabaseClient` instance from anywhere in your app.
```dart
final supabase = Supabase.instance.client;
```
## Other Dart Projects
You can initialize a new Supabase client using the `SupabaseClient()` method.
The Supabase client is your entrypoint to the rest of the Supabase functionality
and is the easiest way to interact with everything we offer within the Supabase ecosystem.
### Dart `SupabaseClient()`
```dart
final supabase = SupabaseClient('https://xyzcompany.supabase.co', 'public-anon-key');
```
@@ -1,24 +0,0 @@
---
id: installing
title: 'Installing'
slug: installing
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
## Flutter
For Flutter project, you can use [supabase_flutter](https://pub.dev/packages/supabase_flutter) package.
```bash
flutter pub add supabase_flutter
```
## Other Dart projects
For non-Flutter projects using Dart, such as server-side Dart or Angular-Dart, you can use the [supabase](https://pub.dev/packages/supabase) package.
```bash
dart pub add supabase
```
-27
View File
@@ -1,27 +0,0 @@
---
id: intro
slug: /
sidebar_label: Supabase Flutter Library
---
# Supabase Flutter Library
This reference documents every object and method available in Supabase's isomorphic Flutter library, [`supabase-flutter`](https://pub.dev/packages/supabase_flutter).
You can use the `supabase-flutter` library to:
- interact with your Postgres database
- listen to database changes
- invoke Deno Edge Functions
- build login and user management functionality
- manage large files
## For non-Flutter projects
We also have [supabase-dart](https://github.com/supabase-community/supabase-dart) for non-Flutter Dart projects, such as server-side Dart or Angular-Dart.
supabase-dart shares most of the APIs with supabase-flutter without being dependent on Flutter so that you can use Supabase anywhere you can run Dart!
## Additional Links
- Source Code: [github.com/supabase/supabase-flutter](https://github.com/supabase/supabase-flutter)
- [Known bugs and issues](https://github.com/supabase/supabase-flutter/issues)
@@ -1,27 +0,0 @@
---
id: intro
slug: /
sidebar_label: Supabase Dart Library
---
# Supabase Dart Library
:::caution
You're viewing the docs for an older version of the `supabase-flutter` library.
:::
This reference documents every object and method available in Supabase's isomorphic Dart library, `supabase-dart`.
You can use the `supabase-dart` library to:
- interact with your Postgres database
- listen to database changes
- invoke Deno Edge Functions
- build login and user management functionality
- manage large files
## Additional Links
- Source Code: [github.com/supabase/supabase-dart](https://github.com/supabase/supabase-dart)
- [Known bugs and issues](https://github.com/supabase/supabase-dart/issues)
@@ -1,120 +0,0 @@
{
"sidebar": [
{
"type": "category",
"label": "Getting Started",
"items": ["intro", "generated/installing", "generated/initializing"],
"collapsed": true
},
{
"type": "category",
"label": "Auth",
"items": [
"generated/auth-signup",
"generated/auth-signin",
"generated/auth-signinwithprovider",
"generated/auth-signout",
"generated/auth-session",
"generated/auth-user",
"generated/auth-update",
"generated/auth-onauthstatechange",
"generated/reset-password-email"
],
"collapsed": true
},
{
"type": "category",
"label": "Functions",
"items": ["generated/invoke"],
"collapsed": true
},
{
"type": "category",
"label": "Database",
"items": [
"generated/select",
"generated/insert",
"generated/update",
"generated/upsert",
"generated/delete",
"generated/rpc"
],
"collapsed": true
},
{
"type": "category",
"label": "Realtime",
"items": [
"generated/subscribe",
"generated/removesubscription",
"generated/getsubscriptions",
"generated/stream"
],
"collapsed": true
},
{
"type": "category",
"label": "Storage",
"items": [
"generated/storage-createbucket",
"generated/storage-getbucket",
"generated/storage-listbuckets",
"generated/storage-updatebucket",
"generated/storage-deletebucket",
"generated/storage-emptybucket",
"generated/storage-from-upload",
"generated/storage-from-download",
"generated/storage-from-list",
"generated/storage-from-update",
"generated/storage-from-move",
"generated/storage-from-remove",
"generated/storage-from-createsignedurl",
"generated/storage-from-getpublicurl"
],
"collapsed": true
},
{
"type": "category",
"label": "Modifiers",
"items": [
"generated/using-modifiers",
"generated/limit",
"generated/order",
"generated/range",
"generated/single"
],
"collapsed": true
},
{
"type": "category",
"label": "Filters",
"items": [
"generated/using-filters",
"generated/or",
"generated/not",
"generated/match",
"generated/eq",
"generated/neq",
"generated/gt",
"generated/gte",
"generated/lt",
"generated/lte",
"generated/like",
"generated/ilike",
"generated/is_",
"generated/in_",
"generated/contains",
"generated/containedby",
"generated/rangelt",
"generated/rangegt",
"generated/rangegte",
"generated/rangelte",
"generated/rangeadjacent",
"generated/overlaps",
"generated/textsearch",
"generated/filter"
],
"collapsed": true
}
]
}
@@ -1 +0,0 @@
["v0"]
@@ -1,37 +0,0 @@
---
id: installing
title: 'Installing'
slug: installing
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supabase.yml
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
All JavaScript libraries are built directly by the Supabase team.
Other languages are built by the community and supported by Supabase.
## JavaScript
Via NPM
```bash
npm install @supabase/supabase-js
```
Via Yarn
```bash
yarn add @supabase/supabase-js
```
Find the source code on [GitHub](https://github.com/supabase/supabase-js).
Or via CDN
```js
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
//or
<script src="https://unpkg.com/@supabase/supabase-js@2"></script>
```
-25
View File
@@ -1,25 +0,0 @@
---
slug: /
sidebar_position: 1
sidebar_label: Supabase JavaScript Library
hide_table_of_contents: true
---
# Supabase JavaScript Library
This reference documents every object and method available in Supabase's isomorphic JavaScript library, `supabase-js`.
You can use the `supabase-js` library to:
- interact with your Postgres database
- listen to database changes
- invoke Deno Edge Functions
- build login and user management functionality
- manage large files
## Additional Links
- Source Code: [github.com/supabase/supabase-js](https://github.com/supabase/supabase-js)
- TypeDoc: [supabase.github.io/supabase-js](https://supabase.github.io/supabase-js/v2/)
- NPM: [npmjs.com/package/@supabase/supabase-js](https://www.npmjs.com/package/@supabase/supabase-js)
- [Known bugs and issues](https://github.com/supabase/supabase-js/issues)
@@ -1,211 +0,0 @@
---
id: release-notes
---
# Release Notes
Supabase.js v2 release notes.
## 2.0.0
Install the latest with `npm install @supabase/supabase-js`.
### Explicit constructor options
All client specific options within the constructor are keyed to the library: [PR](https://github.com/supabase/supabase-js/pull/458):
```jsx
const supabase = createClient(apiURL, apiKey, {
db: {
schema: 'public',
},
auth: {
storage: AsyncStorage,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: true,
},
realtime: {
channels,
endpoint,
},
global: {
fetch: customFetch,
headers: DEFAULT_HEADERS,
},
})
```
### Typescript support
The libraries now support typescript.
```ts
// v2 - definitions are injected in `createClient()`
import type { Database } from './DatabaseDefinitions'
const supabase = createClient<Database>(SUPABASE_URL, ANON_KEY)
const { data } = await supabase.from('messages').select().match({ id: 1 })
// v1 -- previously definitions were injected in the `from()` method
supabase.from<Definitions['Message']>('messages').select('*')
```
Types can be generated via the CLI:
```bash
supabase start
supabase gen types typescript --local > DatabaseDefinitions.ts
```
### Data operations return minimal
`.insert()` / `.upsert()` / `.update()` / `.delete()` don't return rows by default: [PR](https://github.com/supabase/postgrest-js/pull/276).
Previously, these methods return inserted/updated/deleted rows by default (which caused [some confusion](https://github.com/supabase/supabase/discussions/1548)), and you can opt to not return it by specifying `returning: 'minimal'`. Now the default behavior is to not return rows. To return inserted/updated/deleted rows, add a `.select()` call at the end, e.g.:
```sql
const { data, error } = await supabase
.from('my_table')
.delete()
.eq('id', 1)
.select()
```
### New ordering defaults
`.order()` now defaults to Postgres’s default: [PR](https://github.com/supabase/postgrest-js/pull/283).
Previously `nullsFirst` defaults to `false` , meaning `null`s are ordered last. This is bad for performance if e.g. the column uses an index with `NULLS FIRST` (which is the default direction for indexes).
### Cookies and localstorage namespace
Storage key name in the Auth library has changed to include project reference which means that existing websites that had their JWT expiry set to a longer time could find their users logged out with this upgrade.
```jsx
const defaultStorageKey = `sb-${new URL(this.authUrl).hostname.split('.')[0]}-auth-token`
```
### New Auth Types
Typescript typings have been reworked. `Session` interface now guarantees that it will always have an `access_token`, `refresh_token` and `user`
```jsx
interface Session {
provider_token?: string | null
access_token: string
expires_in?: number
expires_at?: number
refresh_token: string
token_type: string
user: User
}
```
### New Auth methods
We're removing the `signIn()` method in favor of more explicit function signatures:
`signInWithPassword()`, `signInWithOtp()`, and `signInWithOtp()`.
```ts
// v2
const { data } = await supabase.auth.signInWithPassword({
email: 'hello@example',
password: 'pass',
})
// v1
const { data } = await supabase.auth.signIn({
email: 'hello@example',
password: 'pass',
})
```
### New Realtime methods
There is a new `channel()` method in the Realtime library, which will be used for our Multiplayer updates.
```ts
supabaseClient
.channel('any_string_you_want')
.on('presence', { event: 'track' }, (payload) => {
console.log(payload)
})
.subscribe()
supabaseClient
.channel('any_string_you_want')
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'movies',
},
(payload) => {
console.log(payload)
}
)
.subscribe()
```
We will deprecate the `.from().on().subscribe()` method previously used for listening to postgres changes.
### Deprecated setAuth()
Deprecated and removed `setAuth()` . To set a custom `access_token` jwt instead, pass the custom header into the `createClient()` method provided: ([PR](https://github.com/supabase/gotrue-js/pull/340))
### All changes
- `supabase-js`
- `shouldThrowOnError` has been removed until all the client libraries support this option ([PR](https://github.com/supabase/supabase-js/pull/490)).
- `postgrest-js`
- TypeScript typings have been reworked [PR](https://github.com/supabase/postgrest-js/pull/279)
- Use `undefined` instead of `null` for function params, types, etc. (https://github.com/supabase/postgrest-js/pull/278)
- Some features are now obsolete: (https://github.com/supabase/postgrest-js/pull/275)
- filter shorthands (e.g. `cs` vs. `contains`)
- `body` in response (vs. `data`)
- `upsert`ing through the `.insert()` method
- `auth` method on `PostgrestClient`
- client-level `throwOnError`
- `gotrue-js`
- `supabase-js` client allows passing a `storageKey` param which will allow the user to set the key used in local storage for storing the session. By default, this will be namespace-d with the supabase project ref. ([PR](https://github.com/supabase/supabase-js/pull/460))
- `signIn` method is now split into `signInWithPassword` , `signInWithOtp` , `signInWithOAuth` ([PR](https://github.com/supabase/gotrue-js/pull/304))
- Deprecated and removed `session()` , `user()` in favor of using `getSession()` instead. `getSession()` will always return a valid session if a user is already logged in, meaning no more random logouts. ([PR](https://github.com/supabase/gotrue-js/pull/299))
- Deprecated and removed setting for `multitab` support because `getSession()` and gotrue’s reuse interval setting takes care of session management across multiple tabs ([PR](https://github.com/supabase/gotrue-js/pull/366))
- No more throwing of random errors, gotrue-js v2 always returns a custom error type: ([PR](https://github.com/supabase/gotrue-js/pull/341))
- `AuthSessionMissingError`
- Indicates that a session is expected but missing
- `AuthNoCookieError`
- Indicates that a cookie is expected but missing
- `AuthInvalidCredentialsError`
- Indicates that the incorrect credentials were passed
- Renamed the `api` namespace to `admin` , the `admin` namespace will only contain methods that should only be used in a trusted server-side environment with the service role key
- Moved `resetPasswordForEmail` , `getUser` and `updateUser` to the `GoTrueClient` which means they will be accessible from the `supabase.auth` namespace in `supabase-js` instead of having to do `supabase.auth.api` to access them
- Removed `sendMobileOTP` , `sendMagicLinkEmail` in favor of `signInWithOtp`
- Removed `signInWithEmail`, `signInWithPhone` in favor of `signInWithPassword`
- Removed `signUpWithEmail` , `signUpWithPhone` in favor of `signUp`
- Replaced `update` with `updateUser`
- `storage-js`
- Return types are more strict. Functions types used to indicate that the data returned could be null even if there was no error. We now make use of union types which only mark the data as null if there is an error and vice versa. ([PR](https://github.com/supabase/storage-js/pull/60))
- The `upload` and `update` function returns the path of the object uploaded as the `path` parameter. Previously the returned value had the bucket name prepended to the path which made it harder to pass the value on to other storage-js methods since all methods take the bucket name and path separately. We also chose to call the returned value `path` instead of `Key` ([PR](https://github.com/supabase/storage-js/pull/75))
- `getPublicURL` only returns the public URL inside the data object. This keeps it consistent with our other methods of returning only within the data object. No error is returned since this method cannot does not throw an error ([PR](https://github.com/supabase/storage-js/pull/93))
- signed urls are returned as `signedUrl` instead of `signedURL` in both `createSignedUrl` and `createSignedUrls` ([PR](https://github.com/supabase/storage-js/pull/94))
- Encodes URLs returned by `createSignedUrl`, `createSignedUrls` and `getPublicUrl` ([PR](https://github.com/supabase/storage-js/pull/86))
- `createsignedUrl` used to return a url directly and and within the data object. This was inconsistent. Now we always return values only inside the data object across all methods. ([PR](https://www.notion.so/LW5-supabase-js-v2-7b0bfcdf571d4f20b9b7a9308883f24b))
- `createBucket` returns a data object instead of the name of the bucket directly. ([PR](https://github.com/supabase/storage-js/pull/89))
- Fixed types for metadata ([PR](https://github.com/supabase/storage-js/pull/90))
- Better error types make it easier to track down what went wrong quicker.
- `SupabaseStorageClient` is no longer exported. Use `StorageClient` instead. ([PR](https://github.com/supabase/storage-js/pull/92)).
- `realtime-js`
- `RealtimeSubscription` class no longer exists and replaced by `RealtimeChannel`.
- `RealtimeClient`'s `disconnect` method now returns type of `void` . It used to return type of `Promise<{ error: Error | null; data: boolean }`.
- Removed `removeAllSubscriptions` and `removeSubscription` methods from `SupabaseClient` class.
- Removed `SupabaseRealtimeClient` class.
- Removed `SupabaseQueryBuilder` class.
- Removed `SupabaseEventTypes` type.
- Thinking about renaming this to something like `RealtimePostgresChangeEvents` and moving it to `realtime-js` v2.
- Removed `.from(’table’).on(’INSERT’, () ⇒ {}).subscribe()` in favor of new Realtime client API.
- `functions-js`
- supabase-js v1 only threw an error if the fetch call itself threw an error (network errors, etc) and not if the function returned HTTP errors like 400s or 500s. We have changed this behaviour to return an error if your function throws an error.
- We have introduced new error types to distinguish between different kinds of errors. A `FunctionsHttpError` error is returned if your function throws an error, `FunctionsRelayError` if the Supabase Relay has an error processing your function and `FunctionsFetchError` if there is a network error in calling your function.
- The correct content-type headers are automatically attached when sending the request if you don’t pass in a `Content-Type` header and pass in an argument to your function. We automatically attach the content type for `Blob`, `ArrayBuffer`, `File`, `FormData` ,`String` . If it doesn’t match any of these we assume the payload is `json` , we serialise the payload as JSON and attach the content type as `application/json`.
- `responseType` does not need to be explicitly passed in. We parse the response based on the `Content-Type` response header sent by the function. We support parsing the responses as `text`, `json`, `blob`, `form-data` and are parsed as `text` by default.
@@ -1,461 +0,0 @@
---
id: upgrade-guide
title: Upgrade to supabase-js v2
description: 'Learn how to upgrade to supabase-js v2.'
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
supabase-js v2 focuses on "quality-of-life" improvements for developers and addresses some of the largest pain points in v1. v2 includes type support, a rebuilt Auth library with async methods, improved errors, and more.
No new features will be added to supabase-js v1 , but we'll continuing merging security fixes to v1, with maintenance patches for the next 3 months.
## Upgrade the client library
```sh
npm install @supabase/supabase-js@2
```
_Optionally_ if you are using custom configuration with `createClient` then follow below:
<Tabs
groupId="version"
values={[
{label: 'Before', value: '1.x'},
{label: 'After', value: '2.x'},
]}>
<TabItem value="1.x">
```ts title="src/supabaseClient.ts"
const supabaseClient = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
schema: 'custom',
persistSession: false,
})
```
</TabItem>
<TabItem value="2.x">
```ts title="src/supabaseClient.ts"
const supabaseClient = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
db: {
schema: 'custom',
},
auth: {
persistSession: true,
},
})
```
</TabItem>
</Tabs>
Read more about the [constructor options](/docs/reference/javascript/release-notes#explicit-constructor-options).
### Auth methods
The signIn() method has been deprecated in favor of more explicit method signatures to help with type hinting. Previously it was difficult for developers to know what they were missing (e.g., a lot of developers didn't realize they could use passwordless magic links).
#### Sign in with email and password
<Tabs
groupId="version"
values={[
{label: 'Before', value: '1.x'},
{label: 'After', value: '2.x'},
]}>
<TabItem value="1.x">
```ts
const { user, error } = await supabase.auth.signIn({ email, password })
```
</TabItem>
<TabItem value="2.x">
```ts
const {
data: { user },
error,
} = await supabase.auth.signInWithPassword({ email, password })
```
</TabItem>
</Tabs>
#### Sign in with magic link
<Tabs
groupId="version"
values={[
{label: 'Before', value: '1.x'},
{label: 'After', value: '2.x'},
]}>
<TabItem value="1.x">
```ts
const { error } = await supabase.auth.signIn({ email })
```
</TabItem>
<TabItem value="2.x">
```ts
const { error } = await supabase.auth.signInWithOtp({ email })
```
</TabItem>
</Tabs>
#### Sign in with a third-party provider
<Tabs
groupId="version"
values={[
{label: 'Before', value: '1.x'},
{label: 'After', value: '2.x'},
]}>
<TabItem value="1.x">
```ts
const { error } = await supabase.auth.signIn({ provider })
```
</TabItem>
<TabItem value="2.x">
```ts
const { error } = await supabase.auth.signInWithOAuth({ provider })
```
</TabItem>
</Tabs>
#### Sign in with phone
<Tabs
groupId="version"
values={[
{label: 'Before', value: '1.x'},
{label: 'After', value: '2.x'},
]}>
<TabItem value="1.x">
```ts
const { error } = await supabase.auth.signIn({ phone, password })
```
</TabItem>
<TabItem value="2.x">
```ts
const { error } = await supabase.auth.signInWithPassword({ phone, password })
```
</TabItem>
</Tabs>
#### Sign in with phone using OTP
<Tabs
groupId="version"
values={[
{label: 'Before', value: '1.x'},
{label: 'After', value: '2.x'},
]}>
<TabItem value="1.x">
```ts
const { error } = await supabase.auth.api.sendMobileOTP(phone)
```
</TabItem>
<TabItem value="2.x">
```ts
const { data, error } = await supabase.auth.signInWithOtp({ phone })
// After receiving a SMS with a OTP.
const { data, error } = await supabase.auth.verifyOtp({ phone, token })
```
</TabItem>
</Tabs>
#### Reset password for email
<Tabs
groupId="version"
values={[
{label: 'Before', value: '1.x'},
{label: 'After', value: '2.x'},
]}>
<TabItem value="1.x">
```ts
const { data, error } = await supabase.auth.api.resetPasswordForEmail(email)
```
</TabItem>
<TabItem value="2.x">
```ts
const { data, error } = await supabase.auth.resetPasswordForEmail(email)
```
</TabItem>
</Tabs>
#### Get the user's current session
<Tabs
groupId="version"
values={[
{label: 'Before', value: '1.x'},
{label: 'After', value: '2.x'},
]}>
<TabItem value="1.x">
```ts
const session = supabase.auth.session()
```
</TabItem>
<TabItem value="2.x">
```ts
const {
data: { session },
} = await supabase.auth.getSession()
```
</TabItem>
</Tabs>
#### Get the logged-in user
<Tabs
groupId="version"
values={[
{label: 'Before', value: '1.x'},
{label: 'After', value: '2.x'},
]}>
<TabItem value="1.x">
```ts
const user = supabase.auth.user()
```
</TabItem>
<TabItem value="2.x">
```ts
const {
data: { session },
} = await supabase.auth.getSession()
const { user } = session
```
</TabItem>
</Tabs>
#### Update user data for a logged-in user
<Tabs
groupId="version"
values={[
{label: 'Before', value: '1.x'},
{label: 'After', value: '2.x'},
]}>
<TabItem value="1.x">
```ts
const { user, error } = await supabase.auth.update({ attributes })
```
</TabItem>
<TabItem value="2.x">
```ts
const {
data: { user },
error,
} = await supabase.auth.updateUser({ attributes })
```
</TabItem>
</Tabs>
#### Use a custom `access_token` JWT with Supabase
<Tabs
groupId="version"
values={[
{label: 'Before', value: '1.x'},
{label: 'After', value: '2.x'},
]}>
<TabItem value="1.x">
```ts
const { user, error } = supabase.auth.setAuth(access_token)
```
</TabItem>
<TabItem value="2.x">
```ts
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
global: {
headers: {
Authorization: `Bearer ${access_token}`,
},
},
})
```
</TabItem>
</Tabs>
### Data methods
`.insert()` / `.upsert()` / `.update()` / `.delete()` don't return rows by default: [PR](https://github.com/supabase/postgrest-js/pull/276).
Previously, these methods return inserted/updated/deleted rows by default (which caused [some confusion](https://github.com/supabase/supabase/discussions/1548)), and you can opt to not return it by specifying `returning: 'minimal'`. Now the default behavior is to not return rows. To return inserted/updated/deleted rows, add a `.select()` call at the end.
#### Insert and return data
<Tabs
groupId="version"
values={[
{label: 'Before', value: '1.x'},
{label: 'After', value: '2.x'},
]}>
<TabItem value="1.x">
```ts
const { data, error } = await supabase.auth.insert({ new_data })
```
</TabItem>
<TabItem value="2.x">
```ts
const { data, error } = await supabase.auth.insert({ new_data }).select()
```
</TabItem>
</Tabs>
#### Update and return data
<Tabs
groupId="version"
values={[
{label: 'Before', value: '1.x'},
{label: 'After', value: '2.x'},
]}>
<TabItem value="1.x">
```ts
const { data, error } = await supabase.auth.update({ new_data }).eq('id', id)
```
</TabItem>
<TabItem value="2.x">
```ts
const { data, error } = await supabase.auth.update({ new_data }).eq('id', id).select()
```
</TabItem>
</Tabs>
### Realtime methods
#### Subscribe
<Tabs
groupId="version"
values={[
{label: 'Before', value: '1.x'},
{label: 'After', value: '2.x'},
]}>
<TabItem value="1.x">
```ts
const userListener = supabase
.from('users')
.on('*', (payload) => handleAllEventsPayload(payload.new))
.subscribe()
```
</TabItem>
<TabItem value="2.x">
```ts
const userListener = supabase.channel('public:user')
.on(
'postgres_changes',
{ event: '*', schema: 'public', table: 'user' }
(payload) => handleAllEventsPayload()
)
.subscribe()
```
</TabItem>
</Tabs>
#### Unsubscribe
<Tabs
groupId="version"
values={[
{label: 'Before', value: '1.x'},
{label: 'After', value: '2.x'},
]}>
<TabItem value="1.x">
```ts
userListener.unsubscribe()
```
</TabItem>
<TabItem value="2.x">
```ts
supabase.removeChannel('public:users')
```
</TabItem>
</Tabs>
@@ -1,81 +0,0 @@
---
id: typescript-support
---
# Typescript Support
`supabase-js` supports Typescript.
## Generating types
You can use our CLI to generate types:
```bash
supabase start
supabase gen types typescript --local > lib/database.types.ts
```
These types are generated directly from your database. Given a table `public.movies`, the definition will provide the following data:
```ts
interface Database {
public: {
Tables: {
movies: {
Row: {} // The data expected to be returned from a "select" statement.
Insert: {} // The data expected passed to an "insert" statement.
Update: {} // The data expected passed to an "update" statement.
}
}
}
}
```
There is a difference between `selects`, `inserts`, and `updates`, because often you will set default values in your database for specific columns.
With default values you do not need to send any data over the network, even if that column is a "required" field. Our type system is granular
enough to handle these situations.
## Injecting type definitions
You can enrich the supabase client with the types you generated with Supabase.
```ts
import { createClient } from '@supabase/supabase-js'
import { Database } from 'lib/database.types'
const supabase = createClient<Database>(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY)
```
## Type hints
`supabase-js` always returns a `data` object (for success), and an `error` response (for unsuccessful requests).
This provides a simple interface to get the relevant types returned from any function:
```ts
export async function getMovies() {
return await supabase.from('movies').select(`id, title`)
}
type MoviesResponse = Awaited<ReturnType<typeof getMovies>>
export type MoviesResponseSuccess = MoviesResponse['data']
export type MoviesResponseError = MoviesResponse['error']
```
## Nested tables
For advanced queries such as nested tables, you may want to construct your own types.
```ts
import supabase from '~/lib/supabase'
import type { Database } from '~/lib/database.types'
async function getMovies() {
return await supabase.from('movies').select('id, title, actors(*)')
}
type Actors = Database['public']['Tables']['actors']['Row']
type MoviesResponse = Awaited<ReturnType<typeof getMovies>>
type MoviesResponseSuccess = MoviesResponse['data'] & {
actors: Actors[]
}
```
@@ -1,42 +0,0 @@
---
id: generating-types
title: 'Generating Types'
slug: generating-types
custom_edit_url: https://github.com/supabase/supabase/edit/master/spec/supabase_js_v1.yml
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
Supabase will soon release native type generators that dump your database types for various languages. For now, we support TypeScript [through third-party tools](/docs/guides/api/generating-types).
## Usage with TypeScript
`supabase-js` ships with type definitions for usage with TypeScript and for convenient IntelliSense auto-complete and documentation in your editor.
When using TypeScript, you can pass the type of database row as a type parameter to the `from` method to get better auto-completion support down the chain.
If you don't provide a type for the row you need to explicitly pass `from<any>('tableName')`.
```ts
type Message = {
id: number
inserted_at: string
message: string
user_id: string
channel_id: number
author: { username: string }
}
const response = await supabase
.from<Message>('messages') // Message maps to the type of the row in your database.
.select('*, author:user_id(username)')
.match({ channel_id: 2 }) // Your IDE will be able to help with auto-completion.
response.data // Response data will be of type Array<Message>.
// If you don't provide a type for the row you need to explicitly pass `from<any>('tableName')`.
const response = await supabase
.from<any>('messages')
.select('*, author:user_id(username)')
.match({ channel_id: 2 })
response.data // Response data will be of type Array<any>.
```
@@ -1,11 +0,0 @@
---
id: index
title: 'Supabase Client'
slug: supabase-client
custom_edit_url: ../../spec/supabase_js_v1.yml
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
Supabase JavaScript.
@@ -1,151 +0,0 @@
---
id: initializing
title: 'Initializing'
slug: initializing
custom_edit_url: https://github.com/supabase/supabase/edit/master/spec/supabase_js_v1.yml
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
You can initialize a new Supabase client using the `createClient()` method.
The Supabase client is your entrypoint to the rest of the Supabase functionality
and is the easiest way to interact with everything we offer within the Supabase ecosystem.
## Parameters
<ul className="method-list-group">
<li className="method-list-item">
<h4 className="method-list-item-label">
<span className="method-list-item-label-name">
supabaseUrl
</span>
<span className="method-list-item-label-badge required">
required
</span>
<span className="method-list-item-validation">
<code>string</code>
</span>
</h4>
<div class="method-list-item-description">
The unique Supabase URL which is supplied when you create a new project in your project dashboard.
</div>
</li>
<li className="method-list-item">
<h4 className="method-list-item-label">
<span className="method-list-item-label-name">
supabaseKey
</span>
<span className="method-list-item-label-badge required">
required
</span>
<span className="method-list-item-validation">
<code>string</code>
</span>
</h4>
<div class="method-list-item-description">
The unique Supabase Key which is supplied when you create a new project in your project dashboard.
</div>
</li>
<li className="method-list-item">
<h4 className="method-list-item-label">
<span className="method-list-item-label-name">
options
</span>
<span className="method-list-item-label-badge false">
optional
</span>
<span className="method-list-item-validation">
<code>SupabaseClientOptions</code>
</span>
</h4>
<div class="method-list-item-description">
No description provided.
</div>
</li>
</ul>
## Examples
### createClient()
```js
import { createClient } from '@supabase/supabase-js'
// Create a single supabase client for interacting with your database
const supabase = createClient(
'https://xyzcompany.supabase.co',
'public-anon-key'
)
```
### With additional parameters
```js
import { createClient } from '@supabase/supabase-js'
const options = {
schema: 'public',
headers: { 'x-my-custom-header': 'my-app-name' },
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: true,
}
const supabase = createClient(
'https://xyzcompany.supabase.co',
'public-anon-key',
options
)
```
### API schemas
```js
import { createClient } from '@supabase/supabase-js'
// Provide a custom schema. Defaults to "public".
const supabase = createClient(
'https://xyzcompany.supabase.co',
'public-anon-key',
{
schema: 'other_schema',
}
)
```
By default the API server points to the `public` schema. You can enable other database schemas within the Dashboard.
Go to `Settings > API > Schema` and add the schema which you want to expose to the API.
Note: each client connection can only access a single schema, so the code above can access the `other_schema` schema but cannot access the `public` schema.
### Custom `fetch` implementation
```js
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'https://xyzcompany.supabase.co',
'public-anon-key',
{
fetch: fetch.bind(globalThis),
}
)
```
`supabase-js` uses the [`cross-fetch`](https://www.npmjs.com/package/cross-fetch) library to make HTTP requests,
but an alternative `fetch` implementation can be provided as an option.
This is most useful in environments where `cross-fetch` is not compatible (for instance Cloudflare Workers).
@@ -1,37 +0,0 @@
---
id: installing
title: 'Installing'
slug: installing
custom_edit_url: https://github.com/supabase/supabase/edit/master/spec/supabase_js_v1.yml
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
All JavaScript libraries are built directly by the Supabase team.
Other languages are built by the community and supported by Supabase.
## JavaScript
Via NPM
```bash
npm install @supabase/supabase-js
```
Via Yarn
```bash
yarn add @supabase/supabase-js
```
Find the source code on [GitHub](https://github.com/supabase/supabase-js).
Or via CDN
```js
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@1"></script>
//or
<script src="https://unpkg.com/@supabase/supabase-js@1"></script>
```
@@ -1,20 +0,0 @@
---
id: intro
title: 'Supabase JavaScript Library'
slug: /
sidebar_label: Supabase JavaScript Library
---
:::caution
You're viewing the docs for an older version of the `supabase-js` library.
:::
This reference documents every object and method available in Supabase's isomorphic JavaScript library, `supabase-js`.
You can use the `supabase-js` library to:
- interact with your Postgres database
- listen to database changes
- invoke Deno Edge Functions
- build login and user management functionality
- manage large files
Whitespace-only changes.
@@ -1,138 +0,0 @@
{
"sidebar": [
{
"type": "category",
"label": "Getting Started",
"items": ["intro", "installing", "initializing", "generating-types"],
"collapsed": false
},
{
"type": "category",
"label": "Auth",
"items": [
"generated/auth-signup",
"generated/auth-signin",
"generated/auth-signout",
"generated/auth-session",
"generated/auth-user",
"generated/auth-update",
"generated/auth-setauth",
"generated/auth-onauthstatechange",
"generated/auth-api-getuser",
"generated/auth-api-resetpasswordforemail"
],
"collapsed": true
},
{
"type": "category",
"label": "Auth (Server Only)",
"items": [
"generated/auth-api-listusers",
"generated/auth-api-createuser",
"generated/auth-api-deleteuser",
"generated/auth-api-generatelink",
"generated/auth-api-inviteuserbyemail",
"generated/auth-api-sendmobileotp",
"generated/auth-api-updateuserbyid"
],
"collapsed": true
},
{
"type": "category",
"label": "Functions",
"items": ["generated/invoke"],
"collapsed": true
},
{
"type": "category",
"label": "Database",
"items": [
"generated/select",
"generated/insert",
"generated/update",
"generated/upsert",
"generated/delete",
"generated/rpc"
],
"collapsed": true
},
{
"type": "category",
"label": "Realtime",
"items": [
"generated/subscribe",
"generated/removesubscription",
"generated/removeallsubscriptions",
"generated/getsubscriptions"
],
"collapsed": true
},
{
"type": "category",
"label": "Storage",
"items": [
"generated/storage-createbucket",
"generated/storage-getbucket",
"generated/storage-listbuckets",
"generated/storage-updatebucket",
"generated/storage-deletebucket",
"generated/storage-emptybucket",
"generated/storage-from-upload",
"generated/storage-from-download",
"generated/storage-from-list",
"generated/storage-from-update",
"generated/storage-from-move",
"generated/storage-from-copy",
"generated/storage-from-remove",
"generated/storage-from-createsignedurl",
"generated/storage-from-createsignedurls",
"generated/storage-from-getpublicurl"
],
"collapsed": true
},
{
"type": "category",
"label": "Modifiers",
"items": [
"generated/using-modifiers",
"generated/limit",
"generated/order",
"generated/range",
"generated/single",
"generated/maybesingle"
],
"collapsed": true
},
{
"type": "category",
"label": "Filters",
"items": [
"generated/using-filters",
"generated/or",
"generated/not",
"generated/match",
"generated/eq",
"generated/neq",
"generated/gt",
"generated/gte",
"generated/lt",
"generated/lte",
"generated/like",
"generated/ilike",
"generated/is",
"generated/in",
"generated/contains",
"generated/containedby",
"generated/rangelt",
"generated/rangegt",
"generated/rangegte",
"generated/rangelte",
"generated/rangeadjacent",
"generated/overlaps",
"generated/textsearch",
"generated/filter"
],
"collapsed": true
}
]
}
@@ -1 +0,0 @@
["v1"]
+51
View File
@@ -0,0 +1,51 @@
import { FC } from 'react'
import { IconInfo, IconHelpCircle, IconAlertTriangle } from 'ui'
interface Props {
type: 'note' | 'tip' | 'info' | 'caution' | 'danger'
label?: string
children: any
}
const Admonition: FC<Props> = ({ type = 'note', label, children }) => {
return (
<div
className={[
'shadow p-4 rounded border-l-[5px] space-y-2 my-4',
`${
type === 'note'
? 'bg-scale-500 border-scale-800'
: type === 'info'
? 'bg-scale-500 border-scale-800'
: type === 'tip'
? 'bg-brand-500 border-brand-800'
: type === 'caution'
? 'bg-yellow-500 border-yellow-800'
: type === 'danger'
? 'bg-red-500 border-red-800'
: 'bg-scale-500 border-scale-800'
}`,
].join(' ')}
>
<div className="flex items-center space-x-2">
{type === 'note' ? (
<IconInfo className="text-scale-1200" size={18} strokeWidth={1.5} />
) : type === 'info' ? (
<IconInfo className="text-scale-1200" size={18} strokeWidth={1.5} />
) : type === 'tip' ? (
<IconHelpCircle className="text-scale-1200" size={18} strokeWidth={1.5} />
) : type === 'caution' ? (
<IconAlertTriangle className="text-scale-1200" size={18} strokeWidth={1.5} />
) : type === 'danger' ? (
<IconAlertTriangle className="text-scale-1200" size={18} strokeWidth={1.5} />
) : (
<IconInfo className="text-scale-1200" size={18} strokeWidth={1.5} />
)}
<p className="text-base text-scale-1200 uppercase my-0">{label || type}</p>
</div>
<div className="admonition-content text-scale-1200 text-base space-y-1">{children}</div>
</div>
)
}
export default Admonition
+41 -18
View File
@@ -1,36 +1,59 @@
import authProviders from '../data/authProviders'
import { Card } from 'ui'
import Link from 'next/link'
import providers from '../data/authProviders'
import ButtonCard from './ButtonCard'
export default function AuthProviders() {
return (
<div className="grid gap-4 md:grid-cols-2">
{authProviders.map((x: any) => (
<div key={x.name}>
<Link passHref href={x.href}>
<Card>
<div className="flex justify-between gap-10">
<p>{x.name}</p>
<p className="flex items-center text-xs font-normal uppercase text-white">
<div className="grid grid-cols-12 gap-4">
{providers.map((x) => (
<div key={x.name} className="col-span-6">
<ButtonCard to={x.href} title={x.name}>
<div className="px-6 py-4">
<div
className=""
style={{
display: 'flex',
justifyContent: 'space-between',
gap: 10,
}}
>
{/* {x.logo && <img src={x.logo} alt={x.name} width="20" />} */}
<p className="mt-0">{x.name}</p>
<p className="mt-0">
{x.official ? (
<span className="bg-brand-800 rounded py-1 px-2">Official</span>
<span className={`badge badge--official`}>Official</span>
) : (
<span className="bg-scale-500 rounded p-1">Unofficial</span>
<span className={`badge badge--unofficial`}>Unofficial</span>
)}
</p>
</div>
<div className="flex flex-col gap-2">
<div className="flex w-full justify-between rounded border-2 border-solid border-gray-600 p-1 text-xs">
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
<div
className="code-block"
style={{
width: '100%',
display: 'flex',
justifyContent: 'space-between',
fontSize: '0.7rem',
}}
>
<span>Platform:</span>
<span>{x.platform.toString()}</span>
</div>
<div className="flex w-full justify-between rounded border-2 border-solid border-gray-600 p-1 text-xs">
<div
className="code-block"
style={{
width: '100%',
display: 'flex',
justifyContent: 'space-between',
fontSize: '0.7rem',
}}
>
<span>Self-Hosted:</span>
<span>{x.selfHosted.toString()}</span>
</div>
</div>
</Card>
</Link>
</div>
</ButtonCard>
</div>
))}
</div>
+2 -2
View File
@@ -34,8 +34,8 @@ export default function ButtonCard({
) : (
icon
)}
<h3 className="my-0 text-base truncate">{title}</h3>
<p className="my-0 text-sm truncate">{description}</p>
<h3 className="my-0 text-base">{title}</h3>
<p className="my-0 text-sm">{description}</p>
</div>
)}
</a>
@@ -4,19 +4,25 @@ import { Button, IconCheck, IconCopy } from 'ui'
import CopyToClipboard from 'react-copy-to-clipboard'
import js from 'react-syntax-highlighter/dist/cjs/languages/hljs/javascript'
import ts from 'react-syntax-highlighter/dist/cjs/languages/hljs/typescript'
import py from 'react-syntax-highlighter/dist/cjs/languages/hljs/python'
import sql from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql'
import bash from 'react-syntax-highlighter/dist/cjs/languages/hljs/bash'
import dart from 'react-syntax-highlighter/dist/cjs/languages/hljs/dart'
import { useState } from 'react'
import { useTheme } from '../Providers'
interface Props {
lang: 'js' | 'sql' | 'py'
lang: 'js' | 'jsx' | 'sql' | 'py' | 'bash' | 'ts' | 'dart'
startingLineNumber?: number
hideCopy?: boolean
className?: string
children?: string
size?: 'small' | 'medium' | 'large'
value?: string
}
function CodeBlock(props: Props) {
const { isDarkMode } = useTheme()
const monokaiTheme = monokaiCustomTheme(isDarkMode)
@@ -37,10 +43,12 @@ function CodeBlock(props: Props) {
: 'js'
// force jsx to be js highlighted
if (lang === 'jsx') lang = 'js'
SyntaxHighlighter.registerLanguage('js', js)
SyntaxHighlighter.registerLanguage('ts', ts)
SyntaxHighlighter.registerLanguage('py', py)
SyntaxHighlighter.registerLanguage('sql', sql)
SyntaxHighlighter.registerLanguage('bash', bash)
SyntaxHighlighter.registerLanguage('dart', dart)
// const large = props.size === 'large' ? true : false
const large = false
@@ -50,41 +58,41 @@ function CodeBlock(props: Props) {
return (
<div className="relative">
<SyntaxHighlighter
language={lang}
style={monokaiTheme}
className={`code-block rounded-lg border p-4 ${!showLineNumbers && 'pl-6'}`}
customStyle={{
fontSize: large ? 18 : 12,
lineHeight: large ? 1.4 : 1.2,
// borderTop: '1px solid #393939',
//background: isDarkMode ? 'bg-scale-700' : 'bg-scale-300',
//background: isDarkMode ? '#444' : '#F1F3F5',
// we really should support proper light mode, not just show dark in both modes
background: isDarkMode ? '#444' : '#F1F3F5',
}}
showLineNumbers={showLineNumbers}
lineNumberContainerStyle={{
paddingTop: '128px',
}}
lineNumberStyle={{
minWidth: '44px',
background: isDarkMode ? '#444' : '#F1F3F5',
paddingLeft: '4px',
paddingRight: '4px',
marginRight: '12px',
color: '#828282',
textAlign: 'center',
fontSize: large ? 14 : 12,
paddingTop: '4px',
paddingBottom: '4px',
}}
>
{props.children?.trimEnd()}
</SyntaxHighlighter>
{!props.hideCopy && props.children ? (
{props.className ? (
<SyntaxHighlighter
language={lang}
style={monokaiTheme}
className={`code-block rounded-lg border p-4 !my-2 !bg-scale-400 ${
!showLineNumbers && 'pl-6'
}`}
customStyle={{
fontSize: large ? 18 : 12,
lineHeight: large ? 1.4 : 1.2,
}}
showLineNumbers={showLineNumbers}
lineNumberContainerStyle={{
paddingTop: '128px',
}}
lineNumberStyle={{
minWidth: '44px',
paddingLeft: '4px',
paddingRight: '4px',
marginRight: '12px',
color: '#828282',
textAlign: 'center',
fontSize: large ? 14 : 12,
paddingTop: '4px',
paddingBottom: '4px',
}}
>
{(props.value || props.children)?.trimEnd()}
</SyntaxHighlighter>
) : (
<code>{props.value || props.children}</code>
)}
{!props.hideCopy && (props.value || props.children) && props.className ? (
<div className={`${isDarkMode ?? 'dark'} absolute right-2 top-2`}>
<CopyToClipboard text={props.children}>
<CopyToClipboard text={props.value || props.children}>
<Button
type="default"
icon={copied ? <IconCheck /> : <IconCopy />}
@@ -1,10 +1,10 @@
import { getAnchor, removeAnchor } from './CustomMDX.utils'
import { getAnchor, removeAnchor } from './CustomHTMLElements.utils'
const H3 = ({ children }) => {
const anchor = getAnchor(children)
const link = `#${anchor}`
return (
<h3 id={anchor} className="group">
<h3 id={anchor} className="group flex gap-1 items-center">
{removeAnchor(children)}
{anchor && (
<a href={link} className="opacity-0 group-hover:opacity-100 transition">
@@ -1,10 +1,10 @@
import { getAnchor, removeAnchor } from './CustomMDX.utils'
import { getAnchor, removeAnchor } from './CustomHTMLElements.utils'
const H4 = ({ children }) => {
const anchor = getAnchor(children)
const link = `#${anchor}`
return (
<h4 id={anchor} className="group">
<h4 id={anchor} className="group flex gap-1 items-center">
{removeAnchor(children)}
{anchor && (
<a href={link} className="opacity-0 group-hover:opacity-100 transition">
@@ -1,10 +1,9 @@
import FooterLinks from 'data/footer.json'
import SectionContainer from './SectionContainer'
import DarkModeToggle from './DarkModeToggle'
import Link from 'next/link'
import { useTheme } from './Providers'
import { Badge } from 'ui'
import Link from 'next/link'
import Image from 'next/image'
import FooterLinks from 'data/footer.json'
import DarkModeToggle from './DarkModeToggle'
import { useTheme } from '../Providers'
import * as supabaseLogoWordmarkDark from 'common/assets/images/supabase-logo-wordmark--dark.png'
import * as supabaseLogoWordmarkLight from 'common/assets/images/supabase-logo-wordmark--light.png'
@@ -19,7 +18,7 @@ const Footer = () => {
<h2 id="footerHeading" className="sr-only">
Footer
</h2>
<SectionContainer>
<div className="sm:py-18 container relative mx-auto px-6 py-16 md:py-24 lg:px-16 lg:py-24 xl:px-20">
<div className="xl:grid xl:grid-cols-3 xl:gap-8">
<div className="space-y-8 xl:col-span-1">
<Link href="#" as="/">
@@ -126,7 +125,7 @@ const Footer = () => {
<small className="small">&copy; Supabase Inc</small>
<DarkModeToggle />
</div>
</SectionContainer>
</div>
</footer>
)
}
+2 -2
View File
@@ -37,8 +37,8 @@ const Frameworks = () => {
{
name: 'Next.js',
logo: {
light: '/docs/img/libraries/nextjs-icon.svg',
dark: '/docs/img/libraries/nextjs-icon.svg',
light: '/docs/img/libraries/nextjs-light-icon.svg',
dark: '/docs/img/libraries/nextjs-dark-icon.svg',
},
href: '/guides/with-nextjs',
},
@@ -0,0 +1,55 @@
import UserManagementSQLTemplate from './user_management_quickstart_sql_template.mdx'
## Project set up
Before we start building we're going to set up our Database and API. This is as simple as starting a new Project in Supabase
and then creating a "schema" inside the database.
### Create a project
1. Go to [app.supabase.com](https://app.supabase.com).
1. Click on "New Project".
1. Enter your project details.
1. Wait for the new database to launch.
### Set up the database schema
Now we are going to set up the database schema. We can use the "User Management Starter" quickstart in the SQL Editor,
or you can just copy/paste the SQL from below and run it yourself.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [SQL Editor](https://app.supabase.com/project/_/sql) page in the Dashboard.
2. Click **User Management Starter**.
3. Click **Run**.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/sql-user-management-starter.mp4" type="video/mp4" muted playsInline />
</video>
</TabPanel>
<TabPanel id="sql" label="SQL">
<UserManagementSQLTemplate />
</TabPanel>
</Tabs>
### Get the API Keys
Now that you've created some database tables, you are ready to insert data using the auto-generated API.
We just need to get the URL and `anon` key from the API settings.
1. Go to the [Settings](https://app.supabase.com/project/_/settings) page in the Dashboard.
2. Click **API** in the sidebar.
3. Find your API `URL`, `anon`, and `service_role` keys on this page.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
</video>
@@ -0,0 +1,11 @@
## Intro
This example provides the steps to build a basic user management app. It includes:
- Supabase [Database](/docs/guides/database): a Postgres database for storing your user data.
- Supabase [Auth](/docs/guides/auth): users can sign in with magic links (no passwords, only email).
- Supabase [Storage](/docs/guides/storage): users can upload a photo.
- [Row Level Security](/docs/guides/auth#row-level-security): data is protected so that individuals can only access their own data.
- Instant [APIs](/docs/guides/api): APIs will be automatically generated when you create your database tables.
By the end of this guide you'll have an app which allows users to login and update some basic profile details:
@@ -0,0 +1,55 @@
```sql
-- Create a table for public profiles
create table profiles (
id uuid references auth.users not null primary key,
updated_at timestamp with time zone,
username text unique,
full_name text,
avatar_url text,
website text,
constraint username_length check (char_length(username) >= 3)
);
-- Set up Row Level Security (RLS)
-- See https://supabase.com/docs/guides/auth/row-level-security for more details.
alter table profiles
enable row level security;
create policy "Public profiles are viewable by everyone." on profiles
for select using (true);
create policy "Users can insert their own profile." on profiles
for insert with check (auth.uid() = id);
create policy "Users can update own profile." on profiles
for update using (auth.uid() = id);
-- This trigger automatically creates a profile entry when a new user signs up via Supabase Auth.
-- See https://supabase.com/docs/guides/auth/managing-user-data#using-triggers for more details.
create function public.handle_new_user()
returns trigger as $$
begin
insert into public.profiles (id, full_name, avatar_url)
values (new.id, new.raw_user_meta_data->>'full_name', new.raw_user_meta_data->>'avatar_url');
return new;
end;
$$ language plpgsql security definer;
create trigger on_auth_user_created
after insert on auth.users
for each row execute procedure public.handle_new_user();
-- Set up Storage!
insert into storage.buckets (id, name)
values ('avatars', 'avatars');
-- Set up access controls for storage.
-- See https://supabase.com/docs/guides/storage#policy-examples for more details.
create policy "Avatar images are publicly accessible." on storage.objects
for select using (bucket_id = 'avatars');
create policy "Anyone can upload an avatar." on storage.objects
for insert with check (bucket_id = 'avatars');
create policy "Anyone can update their own avatar." on storage.objects
for update using (auth.uid() = owner) with check (bucket_id = 'avatars');
```
@@ -26,13 +26,13 @@ export const REFERENCES: References = {
name: 'CLI',
library: undefined,
versions: [],
icon: '/docs/img/libraries/cli-icon.svg',
icon: '/docs/img/icons/cli-icon.svg',
},
api: {
name: 'API',
library: undefined,
versions: [],
icon: '/docs/img/libraries/api-icon.svg',
icon: '/docs/img/icons/api-icon.svg',
},
}
@@ -42,7 +42,7 @@ export const menuItems: NavMenu = {
label: 'Overview',
items: [
{ name: 'Introduction', url: '/', items: [] },
{ name: 'Features', url: '/guides/features', items: [] },
{ name: 'Features', url: '/features', items: [] },
{ name: 'Architecture', url: '/architecture', items: [] },
],
},
@@ -99,9 +99,13 @@ export const menuItems: NavMenu = {
{ name: 'Login with Twitch', url: '/guides/auth/auth-twitch', items: [] },
{ name: 'Login with Twitter', url: '/guides/auth/auth-twitter', items: [] },
{ name: 'Login with WorkOS', url: '/guides/auth/auth-workos', items: [] },
{ name: 'Phone Auth with Twilio', url: '/guides/auth/twilio', items: [] },
{ name: 'Phone Auth with Vonage', url: '/guides/auth/vonage', items: [] },
{ name: 'Phone Auth with MessageBird', url: '/guides/auth/messagebird', items: [] },
{ name: 'Phone Auth with Twilio', url: '/guides/auth/auth-twilio', items: [] },
{ name: 'Phone Auth with Vonage', url: '/guides/auth/auth-vonage', items: [] },
{
name: 'Phone Auth with MessageBird',
url: '/guides/auth/auth-messagebird',
items: [],
},
],
},
{
@@ -147,13 +151,6 @@ export const menuItems: NavMenu = {
},
],
},
{
label: 'APIs',
items: [
{ name: 'Overview', url: '/guides/api', items: [] },
{ name: 'Generating Types', url: '/guides/api/generating-types', items: [] },
],
},
{
label: 'Database',
items: [
@@ -161,11 +158,16 @@ export const menuItems: NavMenu = {
{ name: 'Database Connections', url: '/guides/database/connecting-to-postgres', items: [] },
{ name: 'Tables and Data', url: '/guides/database/tables', items: [] },
{ name: 'Database Functions', url: '/guides/database/functions', items: [] },
{ name: 'Database Webhooks', url: '/guides/database/database-webhooks', items: [] },
{ name: 'Full Text Search', url: '/guides/database/full-text-search', items: [] },
{ name: 'Database Testing', url: '/guides/database/testing', items: [] },
{
name: 'Migrating Between Projects',
url: '/guides/platform/migrating-and-upgrading-projects',
items: [],
name: 'Serverless APIs',
url: undefined,
items: [
{ name: 'Overview', url: '/guides/api', items: [] },
{ name: 'Generating Types', url: '/guides/api/generating-types', items: [] },
],
},
{
name: 'Extensions',
@@ -178,6 +180,16 @@ export const menuItems: NavMenu = {
items: [],
},
{ name: 'http: RESTful Client', url: '/guides/database/extensions/http', items: [] },
{
name: 'pg_cron: Job Scheduling',
url: '/guides/database/extensions/pgcron',
items: [],
},
{
name: 'pg_net: Async Networking',
url: '/guides/database/extensions/pgnet',
items: [],
},
{ name: 'pgTAP: Unit Testing', url: '/guides/database/extensions/pgtap', items: [] },
{
name: 'uuid-ossp: Unique Identifiers',
@@ -213,6 +225,7 @@ export const menuItems: NavMenu = {
{ name: 'Overview', url: '/guides/realtime', items: [] },
{ name: 'Quickstart', url: '/guides/realtime/quickstart', items: [] },
{ name: 'Postgres CDC', url: '/guides/realtime/postgres-cdc', items: [] },
{ name: 'Rate Limits', url: '/guides/realtime/rate-limits', items: [] },
],
},
{
@@ -227,10 +240,15 @@ export const menuItems: NavMenu = {
items: [
{ name: 'Overview', url: '/guides/hosting/platform', items: [] },
{ name: 'Custom Domains', url: '/guides/platform/custom-domains', items: [] },
{ name: 'Disk Space Usage', url: '/guides/platform/disk-usage', items: [] },
{ name: 'Database Usage', url: '/guides/platform/database-usage', items: [] },
{ name: 'Logging', url: '/guides/platform/logs', items: [] },
{ name: 'Metrics', url: '/guides/platform/metrics', items: [] },
{ name: 'Performance', url: '/guides/platform/performance', items: [] },
{
name: 'Migrating and upgrading',
url: '/guides/platform/migrating-and-upgrading-projects',
items: [],
},
{ name: 'Performance Tuning', url: '/guides/platform/performance', items: [] },
{ name: 'Permissions', url: '/guides/platform/permissions', items: [] },
{ name: 'Production Readiness', url: '/guides/platform/going-into-prod', items: [] },
],
@@ -73,7 +73,6 @@ const SideBar = ({ menuItems = [] }: { menuItems: any }) => {
)}
{menuItems.length === 1 ? (
<div className="my-2">
<h4 className="text-scale-1200 pt-2 pb-3">{menuItems[0].label}</h4>
{menuItems[0].items.map((item) => (
<Link href={item.url}>
<a>
@@ -1,7 +1,7 @@
import Link from 'next/link'
import { IconArrowLeft, IconArrowRight } from 'ui'
const pagination = ({ currentPage, totalCount }: { currentPage: number; totalCount: number }) => {
const Pagination = ({ currentPage, totalCount }: { currentPage: number; totalCount: number }) => {
// TODO: not sure if this is the most efficient way to do this. may need to refactor.
const totalArray = Array.from({ length: totalCount }, (_, i: number) => i + 1)
const pages = totalArray.filter((page: number) => {
@@ -50,4 +50,4 @@ const pagination = ({ currentPage, totalCount }: { currentPage: number; totalCou
)
}
export default pagination
export default Pagination
@@ -1,19 +0,0 @@
import classNames from 'classnames'
interface Props {
children: React.ReactNode
className?: string
}
const SectionContainer = ({ children, className }: Props) => (
<div
className={classNames(
`sm:py-18 container relative mx-auto px-6 py-16 md:py-24 lg:px-16 lg:py-24 xl:px-20`,
className
)}
>
{children}
</div>
)
export default SectionContainer
-15
View File
@@ -1,15 +0,0 @@
import Image from 'next/image'
export default function Sponsor({ imageUrl, handle }: { imageUrl: string; handle: string }) {
return (
<div className="my-4 flex h-14 w-44 flex-row items-center">
<Image
className="mr-2 rounded-full"
src={`https://github.com/${handle}.png`}
alt={handle}
width={32}
height={32}
/>
<h5>{handle}</h5>
</div>
)
}
@@ -1,4 +0,0 @@
import { ReactElement } from 'react'
export default function SponsorsWrapper({ children }: { children: ReactElement }) {
return <div className="flex w-full flex-wrap justify-between">{children}</div>
}
-8
View File
@@ -1,8 +0,0 @@
import { Tabs } from 'ui'
export default function TabPanel(props: { id: string; label: string | undefined; children: any }) {
return (
<Tabs.Panel id={props.id} label={props.label}>
{props.children}
</Tabs.Panel>
)
}
-15
View File
@@ -1,15 +0,0 @@
import { Tabs } from 'ui'
const TabWrapper = (props: any) => {
return (
<div className="dashboard-tabs">
<Tabs size="xlarge" type="underlined">
<Tabs.Panel id={props.id} label={props.label}>
{props.children}
</Tabs.Panel>
</Tabs>
</div>
)
}
export default TabWrapper
+19 -7
View File
@@ -1,21 +1,33 @@
import { Tabs, Alert } from 'ui'
// import Tabs from '@theme/Tabs'
// import TabItem from '@theme/TabItem'
import CodeBlock from './CodeBlock/CodeBlock'
import Admonition from './Admonition'
import ButtonCard from './ButtonCard'
import CodeBlock from './CodeBlock/CodeBlock'
import Frameworks from './Frameworks'
import { H3, H4 } from './CustomMDX'
import AuthProviders from './AuthProviders'
import { H3, H4 } from './CustomHTMLElements'
import QuickstartIntro from './MDX/quickstart_intro.mdx'
import ProjectSetup from './MDX/project_setup.mdx'
const components = {
h3: H3,
h4: H4,
Alert,
Admonition,
ButtonCard,
CodeBlock,
Frameworks,
AuthProviders,
QuickstartIntro,
ProjectSetup,
Alert,
Tabs,
TabPanel: (props: any) => <Tabs.Panel {...props}>{props.children}</Tabs.Panel>,
h3: H3,
h4: H4,
code: (props: any) => <CodeBlock {...props} />,
mono: (props: any) => <code className="text-sm">{props.children}</code>,
}
export default components
+57 -14
View File
@@ -20,7 +20,7 @@ const authProviders = [
{
name: 'Apple',
// logo: '/img/libraries/dart-icon.svg',
href: '/docs/guides/auth/auth-apple',
href: '/guides/auth/auth-apple',
official: true,
supporter: 'Supabase',
platform: true,
@@ -29,7 +29,7 @@ const authProviders = [
{
name: 'Azure',
// logo: '/img/libraries/dart-icon.svg',
href: '/docs/guides/auth/auth-azure',
href: '/guides/auth/auth-azure',
official: false,
supporter: 'TBD',
platform: true,
@@ -38,7 +38,7 @@ const authProviders = [
{
name: 'Bitbucket',
// logo: '/img/libraries/dart-icon.svg',
href: '/docs/guides/auth/auth-bitbucket',
href: '/guides/auth/auth-bitbucket',
official: true,
supporter: 'Supabase',
platform: true,
@@ -47,7 +47,7 @@ const authProviders = [
{
name: 'Discord',
// logo: '/img/libraries/dart-icon.svg',
href: '/docs/guides/auth/auth-discord',
href: '/guides/auth/auth-discord',
official: true,
supporter: 'Supabase',
platform: true,
@@ -56,7 +56,7 @@ const authProviders = [
{
name: 'Facebook',
// logo: '/img/libraries/dart-icon.svg',
href: '/docs/guides/auth/auth-facebook',
href: '/guides/auth/auth-facebook',
official: true,
supporter: 'Supabase',
platform: true,
@@ -65,7 +65,7 @@ const authProviders = [
{
name: 'GitHub',
// logo: '/img/libraries/dart-icon.svg',
href: '/docs/guides/auth/auth-github',
href: '/guides/auth/auth-github',
official: true,
supporter: 'Supabase',
platform: true,
@@ -74,7 +74,7 @@ const authProviders = [
{
name: 'GitLab',
// logo: '/img/libraries/dart-icon.svg',
href: '/docs/guides/auth/auth-gitlab',
href: '/guides/auth/auth-gitlab',
official: true,
supporter: 'Supabase',
platform: true,
@@ -83,7 +83,24 @@ const authProviders = [
{
name: 'Google',
// logo: '/img/libraries/dart-icon.svg',
href: '/docs/guides/auth/auth-google',
href: '/guides/auth/auth-google',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
},
{
name: 'Keycloak',
href: '/guides/auth/auth-keycloak',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
},
{
name: 'LinkedIn',
// logo: '/img/libraries/dart-icon.svg',
href: '/guides/auth/auth-linkedin',
official: true,
supporter: 'Supabase',
platform: true,
@@ -92,16 +109,25 @@ const authProviders = [
{
name: 'MessageBird',
// logo: '/img/libraries/dart-icon.svg',
href: '/docs/guides/auth/auth-messagebird',
href: '/guides/auth/auth-messagebird',
official: false,
supporter: 'MessageBird',
platform: true,
selfHosted: true,
},
{
name: 'Notion',
// logo: '/img/libraries/notion-icon.svg',
href: '/guides/auth/auth-notion',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
},
{
name: 'Slack',
// logo: '/img/libraries/dart-icon.svg',
href: '/docs/guides/auth/auth-slack',
href: '/guides/auth/auth-slack',
official: true,
supporter: 'Supabase',
platform: true,
@@ -110,7 +136,7 @@ const authProviders = [
{
name: 'Spotify',
// logo: '/img/libraries/dart-icon.svg',
href: '/docs/guides/auth/auth-spotify',
href: '/guides/auth/auth-spotify',
official: true,
supporter: 'Supabase',
platform: true,
@@ -119,7 +145,7 @@ const authProviders = [
{
name: 'Twitter',
// logo: '/img/libraries/dart-icon.svg',
href: '/docs/guides/auth/auth-twitter',
href: '/guides/auth/auth-twitter',
official: true,
supporter: 'Supabase',
platform: true,
@@ -128,7 +154,16 @@ const authProviders = [
{
name: 'Twitch',
// logo: '/img/libraries/dart-icon.svg',
href: '/docs/guides/auth/auth-twitch',
href: '/guides/auth/auth-twitch',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
},
{
name: 'Zoom',
// logo: '/img/libraries/dart-icon.svg',
href: '/guides/auth/auth-zoom',
official: true,
supporter: 'Supabase',
platform: true,
@@ -137,12 +172,20 @@ const authProviders = [
{
name: 'Twilio',
// logo: '/img/libraries/dart-icon.svg',
href: '/docs/guides/auth/auth-twilio',
href: '/guides/auth/auth-twilio',
official: true,
supporter: 'Supabase',
platform: true,
selfHosted: true,
},
{
name: 'Vonage',
href: '/guides/auth/auth-vonage',
official: false,
supporter: 'Supabase',
platform: true,
selfHosted: true,
},
]
export default authProviders
+1 -1
View File
@@ -2,7 +2,7 @@ const Nav = [
{
label: 'API',
items: [
{ name: 'Supabase API', url: '/reference/api', items: [] },
{ name: 'Management API', url: '/reference/api', items: [] },
{ name: 'Usage', url: '/reference/api/usage', items: [] },
{ name: 'Release Notes', url: '/reference/api/release-notes', items: [] },
],
+1 -1
View File
@@ -16,7 +16,7 @@ while maintaining the scalability required to go beyond it.
Each Supabase project consists of several tools:
![Supabase Architecture](/img/supabase-architecture.png)
![Supabase Architecture](/docs/img/supabase-architecture.png)
### PostgreSQL (Database)
+172
View File
@@ -0,0 +1,172 @@
---
id: features
title: Features
description: 'Supabase features'
---
This is a non-exhaustive list of features that Supabase provides for every project.
## Database
### Postgres Database
Every project is a full Postgres database. [Docs](/docs/guides/database).
### Database Extensions
Every database comes with a full set of Postgres extensions. [Docs](/docs/guides/database/extensions).
### Database Functions
Create custom database functions which you can call from the browser. [Docs](/docs/guides/database/functions).
### Database Triggers
Attach triggers to your tables to handle database changes. [Docs](/docs/guides/auth/managing-user-data#using-triggers).
### Database Webhooks
Send database changes to any external service using Webhooks. [Link](https://supabase.com/blog/supabase-functions-updates#database-webhooks-alpha).
### Database Backups
Projects are backed up daily with the option to upgrade to Point in Time recovery.
### Search
Build search functionality using Postgres Full Text Search. [Docs](/docs/guides/database/full-text-search).
### Secrets and encryption
Encrypt sensitive data and store secrets using our Postgres extension, Supabase Vault. [Link](https://supabase.com/blog/supabase-vault).
### Database migrations
Develop locally and push your changes to your production database using migrations. [Docs](/docs/guides/cli/local-development#database-migrations)
<br />
## Auth
### Email & Password Logins
Build email logins for your application or website. [Docs](/docs/guides/auth/auth-email).
### Magic Links
Build passwordless logins for your application or website.[Docs](/docs/guides/auth/auth-magic-link).
### Social Logins
Provide social logins - everything from Apple, to GitHub, to Slack. [Docs](/docs/guides/auth/auth-apple).
### Phone Logins
Provide phone logins using a 3rd-party SMS provider. [Docs](/docs/guides/auth/auth-twilio).
### Row Level Security
Control the data each user can access with Postgres Policies. [Docs](/docs/guides/auth/row-level-security).
### Serverside Auth Helpers
Helpers for implementing user authentication in popular frameworks like [Next.js](/docs/guides/auth/auth-helpers/nextjs) and [SvelteKit](/docs/guides/auth/auth-helpers/sveltekit)
### Auth UI Kit
Build login and registration pages with custom themes. [Docs](/docs/guides/auth/auth-helpers/auth-ui).
<br />
## APIs & Client libraries
### Auto-generated REST API
RESTful APIs are autogenerated from your database, without a single line of code. [Docs](/docs/guides/api#rest-api-overview).
### Auto-generated GraphQL API
Fast GraphQL APIs using our custom Postgres GraphQL extension. [Docs](/docs/guides/api#graphql-api-overview).
### Realtime Database changes
Receive your database changes through websockets. [Docs](/docs/guides/realtime/postgres-cdc).
### User Broadcasting
Send messages between connected users through websockets. [Docs](/docs/guides/realtime#broadcast).
### User Presence
Synchronize shared state across your users, including online status and typing indicators. [Docs](/docs/guides/realtime#presence).
### Client libraries
Official client libraries for [JavaScript](/docs/reference/javascript/) and [Dart](/docs/reference/dart).
Unofficial libraries [supported by the community](https://github.com/supabase-community#client-libraries).
<br />
## File Storage
### Large File storage
Supabase Storage makes it simple to store and serve large files. [Docs](/docs/guides/storage).
### Storage CDN
Cache large files using the Supabase CDN. [Docs](/docs/guides/storage-cdn).
<br />
## Edge Functions
### Deno Edge Functions
Globally distributed TypeScript functions to execute custom business logic. [Docs](/docs/guides/functions).
<br />
## Project Managment
### CLI
Use our CLI to develop your project locally and deploy to the Supabase Platform. [Docs](/docs/reference/cli).
### Admin API
Manage your projects programmatically. [Docs](/docs/reference/api).
<br />
## Feature Status
Both Postgres and the Supabase Platform are production-ready. Some tools we offer on top of Postgres are still under development.
| Product | Feature | Stage |
| -------------------------- | ---------------------- | ------- |
| Database | Postgres | `GA` |
| Database | Triggers | `GA` |
| Database | Functions | `GA` |
| Database | Extensions | `GA` |
| Database | Full Text Search | `GA` |
| Database | Webhooks | `alpha` |
| Database | Point-in-Time Recovery | `alpha` |
| Database | Vault | `alpha` |
| Studio | | `GA` |
| Realtime | Postgres CDC | `beta` |
| Realtime | Broadcast | `alpha` |
| Realtime | Presence | `alpha` |
| Storage | Backend (S3) | `GA` |
| Storage | API | `beta` |
| Storage | CDN | `beta` |
| Edge Functions | | `beta` |
| Auth | OAuth Providers | `beta` |
| Auth | Passwordless | `beta` |
| Auth | Next.js Auth Helpers | `alpha` |
| Auth | SvelteKit Auth Helpers | `alpha` |
| Auth | Remix Auth Helpers | `alpha` |
| Admin API | | `beta` |
| CLI | | `beta` |
| Client Library: JavaScript | | `GA` |
| Client Library: Dart | | `beta` |
+61 -76
View File
@@ -1,17 +1,11 @@
---
id: api
title: APIs
title: Serverless APIs
description: Auto-generating and Realtime APIs.
sidebar_label: Overview
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
// import useBaseUrl from '@docusaurus/useBaseUrl'
## Overview
Supabase generates three types of API directly from your database schema.
Supabase auto-generates three types of API directly from your database schema.
- REST - interact with your database through a restful interface.
- Realtime - listen to database changes.
@@ -25,7 +19,7 @@ The APIs are:
- **Fast.** <br />Our benchmarks for basic reads are more than 300% faster than Firebase. The API is a very thin layer on top of Postgres, which does most of the heavy lifting.
- **Scalable.** <br />The API can serve thousands of simultaneous requests, and works well for Serverless workloads.
### REST API {#rest-api-overview}
## REST API {#rest-api-overview}
Supabase provides a RESTful API using [PostgREST](https://postgrest.org/). This is a very thin API layer on top of Postgres.
It provides everything you need from a CRUD API:
@@ -45,17 +39,17 @@ It provides everything you need from a CRUD API:
></iframe>
</div>
### GraphQL API {#graphql-api-overview}
## GraphQL API {#graphql-api-overview}
:::note
<Admonition type='note'>
GraphQL is in Beta, and may have breaking changes. It is only available on self-hosted setups and Supabase projects created after 28th March 2022.
:::
</Admonition>
GraphQL in Supabase works through [pg_graphql](https://supabase.com/blog/pg-graphql), an open source PostgreSQL extension for GraphQL.
### Realtime API {#realtime-api-overview}
## Realtime API {#realtime-api-overview}
Supabase provides a Realtime API using [Realtime](https://github.com/supabase/realtime). You can use this to listen to database changes over websockets.
Realtime leverages PostgreSQL's built-in logical replication. You can manage your Realtime API simply by managing Postgres publications.
@@ -73,14 +67,12 @@ API route by creating a table called `todos` to store tasks.
This creates a corresponding route `todos` which can accept `GET`, `POST`, `PATCH`, & `DELETE` requests.
<Tabs
groupId="dashboard-or-sql"
defaultValue="dashboard"
values={[
{label: 'Dashboard', value: 'dashboard'},
{label: 'SQL', value: 'sql'},
]}>
<TabItem value="dashboard">
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Table editor](https://app.supabase.com/project/_/editor) page in the Dashboard.
1. Click **New Table** and create a table with the name `todos`.
@@ -89,11 +81,11 @@ This creates a corresponding route `todos` which can accept `GET`, `POST`, `PATC
1. Click **Save**.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/api/api-create-table-sm.mp4" type="video/mp4" muted playsInline />
<source src={'/docs/videos/api/api-create-table-sm.mp4'} type="video/mp4" muted playsInline />
</video>
</TabItem>
<TabItem value="sql">
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
-- Create a table called "todos" with a column to store tasks.
@@ -102,9 +94,10 @@ create table todos (
id bigint generated by default as identity primary key,
task text check (char_length(task) > 3)
);
```
</TabItem>
</TabPanel>
</Tabs>
### API URL and Keys
@@ -116,7 +109,7 @@ Every Supabase project has a unique API URL. Your API is secured behind an API g
3. Find your API `URL`, `anon`, and `service_role` keys on this page.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
<source src={'/docs/videos/api/api-url-and-key.mp4'} type="video/mp4" muted playsInline />
</video>
The REST API and the GraphQL API are both accessible through this URL:
@@ -145,7 +138,7 @@ Let's view the documentation for a `countries` table which we created in our dat
3. Switch between the JavaScript and the cURL docs using the tabs.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/api/api-docs.mp4" type="video/mp4" muted playsInline />
<source src={'/docs/videos/api/api-docs.mp4'} type="video/mp4" muted playsInline />
</video>
#### GraphQL
@@ -168,14 +161,12 @@ Let's see how to make a request to the `todos` table which we created in the fir
using the API URL (`SUPABASE_URL`) and Key (`SUPABASE_ANON_KEY`) we provided:
<Tabs
groupId="language"
defaultValue="javascript"
values={[
{label: 'JavaScript', value: 'javascript'},
{label: 'cURL', value: 'curl'},
]}>
<TabItem value="javascript">
scrollable
size="small"
type="underlined"
defaultActiveId="javascript"
>
<TabPanel id="javascript" label="Javascript">
```javascript
// Initialize the JS client
@@ -186,8 +177,8 @@ const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
const { data: todos, error } = await supabase.from('todos').select('*')
```
</TabItem>
<TabItem value="curl">
</TabPanel>
<TabPanel id="curl" label="cURL">
```bash
# Append /rest/v1/ to your URL, and then use the table name as the route
@@ -196,7 +187,7 @@ curl '<SUPABASE_URL>/rest/v1/todos' \
-H "Authorization: Bearer <SUPABASE_ANON_KEY>"
```
</TabItem>
</TabPanel>
</Tabs>
JS Reference: [select()](../reference/javascript/select),
@@ -208,24 +199,22 @@ JS Reference: [select()](../reference/javascript/select),
### GraphQL API
:::note
<Admonition type='note'>
To rebuild your GraphQL schema from the SQL schema, call `select graphql.rebuild_schema();`.
Be sure to rebuild the GraphQL schema after altering the SQL schema.
:::
</Admonition>
You can use any GraphQL client with the Supabase GraphQL API. For our GraphQL example we will use [urql](https://formidable.com/open-source/urql/docs/).
<Tabs
groupId="language"
defaultValue="javascript"
values={[
{label: 'JavaScript', value: 'javascript'},
{label: 'cURL', value: 'curl'},
]}>
<TabItem value="javascript">
scrollable
size="small"
type="underlined"
defaultActiveId="javascript"
>
<TabPanel id="javascript" label="Javascript">
```javascript
import { createClient, useQuery } from 'urql'
@@ -268,8 +257,8 @@ const [result, reexecuteQuery] = useQuery({
const { data, fetching, error } = result
```
</TabItem>
<TabItem value="curl">
</TabPanel>
<TabPanel id="curl" label="cURL">
```bash
# Append /graphql/v1/ to your URL, and then use the table name as the route
@@ -279,7 +268,7 @@ curl --request POST '<SUPABASE_URL>/graphql/v1' \
-d '{ "query":"{ todos(first: 3) { edges { node { id } } } }" }'
```
</TabItem>
</TabPanel>
</Tabs>
### Realtime API
@@ -287,14 +276,12 @@ curl --request POST '<SUPABASE_URL>/graphql/v1' \
By default Realtime is disabled on your database. Let's turn on Realtime for the `todos` table.
<Tabs
groupId="dashboard-or-sql"
defaultValue="dashboard"
values={[
{label: 'Dashboard', value: 'dashboard'},
{label: 'SQL', value: 'sql'},
]}>
<TabItem value="dashboard">
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard.
2. Click on **Replication** in the sidebar.
@@ -302,17 +289,17 @@ By default Realtime is disabled on your database. Let's turn on Realtime for the
4. Control which tables broadcast changes by selecting **Source** and toggling each table.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/api/api-realtime.mp4" type="video/mp4" muted playsInline />
<source src={'/docs/videos/api/api-realtime.mp4'} type="video/mp4" muted playsInline />
</video>
</TabItem>
<TabItem value="sql">
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
alter publication supabase_realtime add table todos;
```
</TabItem>
</TabPanel>
</Tabs>
From the client, we can listen to any new data that is inserted into the `todos` table:
@@ -344,27 +331,25 @@ To control access to your data, you can use [Policies](/guides/auth#policies).
When you create a table in Postgres, Row Level Security is disabled by default. To enable RLS:
<Tabs
groupId="dashboard-or-sql"
defaultValue="dashboard"
values={[
{label: 'Dashboard', value: 'dashboard'},
{label: 'SQL', value: 'sql'},
]}>
<TabItem value="dashboard">
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Authentication](https://app.supabase.com/project/_/auth/users) page in the Dashboard.
2. Click on **Policies** in the sidebar.
3. Select **Enable RLS** to enable Row Level Security.
</TabItem>
<TabItem value="sql">
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
alter table todos enable row level security;
```
</TabItem>
</TabPanel>
</Tabs>
### The `service_role` key
@@ -386,6 +371,6 @@ select usename,useconfig from pg_shadow where usename = 'authenticator' ;
The expected value for `useconfig` should be:
```
["session_preload_libraries=supautils, safeupdate"]
```sql
['session_preload_libraries=supautils, safeupdate']
```
@@ -6,71 +6,59 @@ description: How to generate types for your API and Supabase libraries.
Supabase APIs are generated from your database, which means that we can use database introspection to generate type-safe API definitions.
### Generating types from OpenAPI specification
### Generating types using Supabase CLI
Supabase generates an OpenAPI specification file for your database which can be used to generate your data types for usage with TypeScript.
The Supabase CLI is a single binary Go application that provides everything you need to setup a local development environment.
The OpenAPI specification for your Supabase project can be accessed as follows:
```txt
https://your-project.supabase.co/rest/v1/?apikey=your-anon-key
```
Using the open source [openapi-typescript](https://github.com/drwpow/openapi-typescript#%EF%B8%8F-reading-specs-from-remote-resource) tool you can generate your types and store them locally:
You can [install the CLI](https://www.npmjs.com/package/supabase) via npm or other supported package managers. The minimum required version of the CLI is [v1.8.1](https://github.com/supabase/cli/releases).
```bash
npx openapi-typescript https://your-project.supabase.co/rest/v1/?apikey=your-anon-key --output types/supabase.ts
npm i supabase@">=1.8.1" --save-dev
```
Important notes:
Login with your Personal Access Token
- Since the generator uses JSON API, there is no way to determine if a column is an Array. It will generate array types as `string`, even though Supabase handles this automatically and returns arrays.
You can fix this manually in the files by changing the type, e.g. `names: string` -> `names: string[]`
- The types won't automatically stay in sync with your database, so make sure to regenerate your types after you make changes to your database.
```bash
npx supabase login
```
After you have generated your types, you can use them in your TypeScript projects:
Generate types for your project to produce the file `types/supabase.ts`
```ts
```bash
npx supabase gen types typescript --project-id "$PROJECT_ID" --schema public > types/supabase.ts
```
After you have generated your types, you can use them in `src/index.ts`
```tsx
import { NextApiRequest, NextApiResponse } from 'next'
import { createClient } from '@supabase/supabase-js'
import { definitions } from '../../types/supabase'
import { Database } from '../types/supabase'
const supabase = createClient(
const supabase = createClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.SUPABASE_SECRET_KEY
)
export default async (req: NextApiRequest, res: NextApiResponse) => {
const allOnlineUsers = await supabase
.from<definitions['users']>('users')
.select('*')
.eq('status', 'ONLINE')
const allOnlineUsers = await supabase.from('users').select('*').eq('status', 'ONLINE')
res.status(200).json(allOnlineUsers)
}
```
For more advance type-support, check out [`postgrest-js-tools`](https://github.com/mzalevski/postgrest-js-tools).
## Update types automatically with GitHub Actions
One way to keep your type definitions in sync with your database is to set up a GitHub action that runs on a schedule.
The following script can be run in your terminal to produce the file `types/database/index.ts`.
Add the script above to your `package.json` to run it using `npm run update-types`
```
npx openapi-typescript https://your-project.supabase.co/rest/v1/?apikey=your-anon-key --output types/database/index.ts
```json
"update-types": "npx supabase gen types typescript --project-id \"$PROJECT_ID\" > types/supabase.ts"
```
You can add this script to your `package.json` and run it using `npm run update-types`:
Create a file `.github/workflows/update-types.yml` with the following snippet to define the action along with the environment variables. This script will commit new type changes to your repo every night.
```
"update-types": "npx openapi-typescript \"${SUPABASE_URL}/rest/v1/?apikey=${SUPABASE_ANON_KEY}\" --version=2 --output types/database/index.ts"
```
You can use GitHub actions to generate this file automatically. This script will commit the change to your repo every night.
Create a file `.github/workflows/update-types.yml` and add the following snippet into this file to define the action along with the environment variables.
```yml
```yaml
name: Update database types
on:
@@ -82,8 +70,8 @@ jobs:
update:
runs-on: ubuntu-latest
env:
SUPABASE_URL: ${{secrets.SUPABASE_URL}}
SUPABASE_ANON_KEY: ${{secrets.SUPABASE_ANON_KEY}}
SUPABASE_ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
PROJECT_ID: <your-project-id>
steps:
- uses: actions/checkout@v2
with:
@@ -91,7 +79,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-node@v2.1.5
with:
node-version: 14
node-version: 16
- run: npm run update-types
- name: check for file changes
id: git_status
+2 -62
View File
@@ -5,13 +5,6 @@ description: Use Supabase to Authenticate and Authorize your users.
sidebar_label: Overview
---
import Link from '@docusaurus/Link'
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
import providers from '../../data/authProviders'
import ButtonCard from '@site/src/components/ButtonCard'
import useBaseUrl from '@docusaurus/useBaseUrl'
## Overview
There are two parts to every Auth system:
@@ -47,67 +40,14 @@ You can authenticate your users in several ways:
We provide a suite of Providers and login methods, as well as [Auth helpers](/docs/guides/auth/auth-helpers/).
<div class="container" style={{ padding: 0 }}>
<div class="row is-multiline">
{providers.map((x) => (
<div key={x.name} class="col col--6">
<ButtonCard class="card" to={useBaseUrl(x.href)} title={x.name} style={{ height: '100%' }}>
<div class="button-card__inner">
<div
class=""
style={{
display: 'flex',
justifyContent: 'space-between',
gap: 10,
}}
>
{x.logo && <img src={x.logo} alt={x.name} width="20" />}
<p>{x.name}</p>
<p>
{x.official ? (
<span class={`badge badge--official`}>Official</span>
) : (
<span class={`badge badge--unofficial`}>Unofficial</span>
)}
</p>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
<div
class="code-block"
style={{
width: '100%',
display: 'flex',
justifyContent: 'space-between',
fontSize: '0.7rem',
}}
>
<span>Platform:</span>
<span>{x.platform.toString()}</span>
</div>
<div
class="code-block"
style={{
width: '100%',
display: 'flex',
justifyContent: 'space-between',
fontSize: '0.7rem',
}}
>
<span>Self-Hosted:</span>
<span>{x.selfHosted.toString()}</span>
</div>
</div>
</div>
</ButtonCard>
</div>
))}
</div>
<AuthProviders />
</div>
### Configure third-party providers
You can enable third-party providers with the click of a button by navigating to Authentication > Settings > Auth Providers and inputting your `Client ID` and `Secret` for each.
![OAuth Logins.](/img/supabase-oauth-logins.png)
![OAuth Logins.](/docs/img/supabase-oauth-logins.png)
## Authorization
@@ -25,7 +25,7 @@ Apple OAuth consists of six broad steps:
- Go to [developer.apple.com](https://developer.apple.com).
- Click on `Account` at the top right to log in.
![Apple Developer Portal.](/img/guides/auth-apple/apple-developer-portal.png)
![Apple Developer Portal.](/docs/img/guides/auth-apple/apple-developer-portal.png)
## Obtain an App ID
@@ -69,12 +69,7 @@ The next step requires a callback URL, which looks like this:
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
<video width="99%" muted playsInline controls="true">
<source
src="/docs/videos/api/api-url-and-key.mp4"
type="video/mp4"
muted
playsInline
/>
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
</video>
## Configure your Services ID
@@ -32,7 +32,7 @@ Azure OAuth consists of four broad steps:
- The redirect / callback URI should look like this: `https://<project-ref>.supabase.co/auth/v1/callback`
- Click "Register" at the bottom of the form.
![Register an application.](/img/guides/auth-azure/azure-register-app.png)
![Register an application.](/docs/img/guides/auth-azure/azure-register-app.png)
## Obtain a Client ID
@@ -40,7 +40,7 @@ This will serve as the `client_id` when you make API calls to authenticate the u
- Once your app has been registered, the client id can be found under the [list of app registrations](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps) under the column titled "Application (client) ID".
![Obtain the client id](/img/guides/auth-azure/azure-client-id.png)
![Obtain the client id](/docs/img/guides/auth-azure/azure-client-id.png)
## Obtain a Secret ID
@@ -52,7 +52,7 @@ This will serve as the `client_secret` when you make API calls to authenticate t
- Enter a description and choose your preferred expiry for the secret.
- Once the secret is generated, save the `value` (not the secret ID).
![Obtain the client secret](/img/guides/auth-azure/azure-client-secret.png)
![Obtain the client secret](/docs/img/guides/auth-azure/azure-client-secret.png)
## Obtain the Tenant URL
@@ -61,13 +61,15 @@ This will allow your users to use your custom Azure login page when logging in.
- Select the Directory (Tenant) ID value.
- The Azure Tenant URL should look like this: `https://login.microsoftonline.com/<tenant-id>`
![Obtain the tenant url](/img/guides/auth-azure/azure-tenant-url.png)
![Obtain the tenant url](/docs/img/guides/auth-azure/azure-tenant-url.png)
## Add login code to your client app
:::tip
<Admonition type="tip">
Supabase Auth requires that Azure returns a valid email address. Therefore you must request the `email` scope in the `signIn` method above.
:::
</Admonition>
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `azure` as the `provider`:
@@ -22,7 +22,7 @@ Setting up Bitbucket logins for your application consists of 3 parts:
- Go to [bitbucket.org](https://bitbucket.org/).
- Click on `Login` at the top right to log in.
![Bitbucket Developer Portal.](/img/guides/auth-bitbucket/bitbucket-portal.png)
![Bitbucket Developer Portal.](/docs/img/guides/auth-bitbucket/bitbucket-portal.png)
## Find your callback URL
@@ -37,12 +37,7 @@ The next step requires a callback URL, which looks like this:
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
<video width="99%" muted playsInline controls="true">
<source
src="/docs/videos/api/api-url-and-key.mp4"
type="video/mp4"
muted
playsInline
/>
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
</video>
## Create a Bitbucket OAuth app
@@ -17,21 +17,21 @@ Go to the [hCaptcha](https://www.hcaptcha.com/) website and sign up for an accou
If you have already signed up and didn't copy this information from the welcome page, you can get the **Secret key** from the settings page.
![site_secret_settings.png](/img/guides/auth-captcha/site_secret_settings.png)
![site_secret_settings.png](/docs/img/guides/auth-captcha/site_secret_settings.png)
The **Sitekey** can be found in the **Settings** of the active site you created.
![sites_dashboard.png](/img/guides/auth-captcha/sites_dashboard.png)
![sites_dashboard.png](/docs/img/guides/auth-captcha/sites_dashboard.png)
In the Settings page, look for the **Sitekey** section and copy the key.
![sitekey_settings.png](/img/guides/auth-captcha/sitekey_settings.png)
![sitekey_settings.png](/docs/img/guides/auth-captcha/sitekey_settings.png)
### Enable hCaptcha protection for your Supabase project
Navigate to the **[Authentication](https://app.supabase.com/project/_/auth/settings)** page in the Supabase Dashboard and find the **Enable hCaptcha protection** toggle under the **Security and Protection** section.
![supabase_auth_general_settings.png](/img/guides/auth-captcha/supabase_auth_general_settings.png)
![supabase_auth_general_settings.png](/docs/img/guides/auth-captcha/supabase_auth_general_settings.png)
Enter your hCaptcha **Secret key** and click **Save**.
@@ -22,11 +22,11 @@ Setting up Discord logins for your application consists of 3 parts:
- Go to [discord.com](https://discord.com/).
- Click on `Login` at the top right to log in.
![Discord Portal.](/img/guides/auth-discord/discord-portal.png)
![Discord Portal.](/docs/img/guides/auth-discord/discord-portal.png)
- Once logged in, go to [discord.com/developers](https://discord.com/developers).
![Discord Portal.](/img/guides/auth-discord/discord-developer-portal.png)
![Discord Portal.](/docs/img/guides/auth-discord/discord-developer-portal.png)
## Find your callback URL
@@ -41,12 +41,7 @@ In the next step you require a callback URL, which looks like this:
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
<video width="99%" muted playsInline controls="true">
<source
src="/docs/videos/api/api-url-and-key.mp4"
type="video/mp4"
muted
playsInline
/>
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
</video>
## Create a Discord Application
+18 -22
View File
@@ -20,22 +20,22 @@ Setting up Email logins for your Supabase application.
1. For **Site URL**, enter the final (hosted) URL of your app.
1. Under Auth Providers, **enable email provider**.
:::note Self hosting
<Admonition type="info" label="Self hosting">
For self-hosting, you can update your project configuration using the files and environment variables provided.
See the [self-hosting docs](/docs/guides/hosting/overview#configuration) for details.
:::
</Admonition>
## Add login code to your client app
<Tabs
defaultValue="js"
groupId="guides/auth"
values={[{ label: 'JavaScript', value: 'js' }, { label: 'Dart', value: 'dart' }]}
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabItem value="js">
<TabPanel id="js" label="JavaScript">
When your user signs in, call [signInWithPassword()](/docs/reference/javascript/auth-signinwithpassword) with their email address and password:
@@ -48,9 +48,8 @@ async function signInWithEmail() {
}
```
</TabItem>
<TabItem value="dart">
</TabPanel>
<TabPanel id="dart" label="Dart">
When your user signs in, call [signInWithPassword()](/docs/reference/dart/auth-signinwithpassword) with their email address and password:
@@ -63,17 +62,16 @@ Future<void> signInWithEmail() async {
}
```
</TabItem>
</TabPanel>
</Tabs>
<Tabs
defaultValue="js"
groupId="guides/auth"
values={[{ label: 'JavaScript', value: 'js' }, { label: 'Dart', value: 'dart' }]}
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabItem value="js">
<TabPanel id="js" label="JavaScript">
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
@@ -83,9 +81,8 @@ async function signOut() {
}
```
</TabItem>
<TabItem value="dart">
</TabPanel>
<TabPanel id="dart" label="Dart">
When your user signs out, call [signOut()](/docs/reference/dart/auth-signout) to remove them from the browser session and any objects from localStorage:
@@ -95,8 +92,7 @@ Future<void> signOut() async {
}
```
</TabItem>
</TabPanel>
</Tabs>
## Resources
@@ -22,7 +22,7 @@ Setting up Facebook logins for your application consists of 3 parts:
- Go to [developers.facebook.com](https://developers.facebook.com).
- Click on `Log In` at the top right to log in.
![Facebook Developer Portal.](/img/guides/auth-facebook/facebook-portal.png)
![Facebook Developer Portal.](/docs/img/guides/auth-facebook/facebook-portal.png)
## Create a Facebook App
@@ -45,12 +45,7 @@ The next step requires a callback URI, which looks like this:
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
<video width="99%" muted playsInline controls="true">
<source
src="/docs/videos/api/api-url-and-key.mp4"
type="video/mp4"
muted
playsInline
/>
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
</video>
## Set up FaceBook Login for your Facebook App
@@ -22,7 +22,7 @@ Setting up GitHub logins for your application consists of 3 parts:
- Go to [github.com](https://github.com).
- Click on `Sign In` at the top right to log in.
![GitHub Developer Portal.](/img/guides/auth-github/github-portal.png)
![GitHub Developer Portal.](/docs/img/guides/auth-github/github-portal.png)
## Create a GitHub Oauth App
@@ -46,12 +46,7 @@ The next step requires a callback URL, which looks like this:
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
<video width="99%" muted playsInline controls="true">
<source
src="/docs/videos/api/api-url-and-key.mp4"
type="video/mp4"
muted
playsInline
/>
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
</video>
## Register a new OAuth application
@@ -22,7 +22,7 @@ Setting up GitLab logins for your application consists of 3 parts:
- Go to [gitlab.com](https://gitlab.com).
- Click on `Login` at the top right to log in.
![GitLab Developer Portal.](/img/guides/auth-gitlab/gitlab-portal.png)
![GitLab Developer Portal.](/docs/img/guides/auth-gitlab/gitlab-portal.png)
## Find your callback URL
@@ -37,12 +37,7 @@ The next step requires a callback URL, which looks like this:
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
<video width="99%" muted playsInline controls="true">
<source
src="/docs/videos/api/api-url-and-key.mp4"
type="video/mp4"
muted
playsInline
/>
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
</video>
## Create your GitLab Application
@@ -22,7 +22,7 @@ Setting up Google logins for your application consists of 3 parts:
- Go to [cloud.google.com](https://cloud.google.com).
- Click on `Sign in` at the top right to log in.
![Google Developer Portal.](/img/guides/auth-google/google-portal.png)
![Google Developer Portal.](/docs/img/guides/auth-google/google-portal.png)
## Create a Google Cloud Platform Project
@@ -60,12 +60,7 @@ The next step requires a callback URL, which looks like this:
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
<video width="99%" muted playsInline controls="true">
<source
src="/docs/videos/api/api-url-and-key.mp4"
type="video/mp4"
muted
playsInline
/>
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
</video>
## Create your Google credentials
@@ -5,56 +5,49 @@ description: A collection of framework-specific Auth utilities for working with
sidebar_label: Overview
---
import useBaseUrl from '@docusaurus/useBaseUrl'
import ButtonCard from '@site/src/components/ButtonCard'
A collection of framework-specific Auth utilities for working with Supabase.
<div class="container" style={{ padding: 0 }}>
<div class="row is-multiline">
<div class="grid grid-cols-12 gap-4">
{/* Auth UI */}
<div class="col col--4">
<div class="col-span-6">
<ButtonCard
class="card"
to={useBaseUrl('/guides/auth/auth-helpers/auth-ui')}
to={'/guides/auth/auth-helpers/auth-ui'}
title={'Auth UI'}
description={'A pre-built React component for authenticating users.'}
style={{ height: '100%' }}
/>
</div>
{/* Next.js */}
<div class="col col--4">
<div class="col-span-6">
<ButtonCard
class="card"
to={useBaseUrl('/guides/auth/auth-helpers/nextjs')}
to={'/guides/auth/auth-helpers/nextjs'}
title={'Next.js'}
description={
'Helpers for authenticating users in Next.js applications.'
}
description={'Helpers for authenticating users in Next.js applications.'}
style={{ height: '100%' }}
/>
</div>
{/* SvelteKit */}
<div class="col col--4">
<div class="col-span-6">
<ButtonCard
class="card"
to={useBaseUrl('/guides/auth/auth-helpers/sveltekit')}
to={'/guides/auth/auth-helpers/sveltekit'}
title={'SvelteKit'}
description={
'Helpers for authenticating users in SvelteKit applications.'
}
description={'Helpers for authenticating users in SvelteKit applications.'}
style={{ height: '100%' }}
/>
</div>
{/* Remix */}
<div class="col col--4">
<div class="col-span-6">
<ButtonCard
class="card"
to={useBaseUrl('/guides/auth/auth-helpers/remix')}
to={'/guides/auth/auth-helpers/remix'}
title={'Remix'}
description={
'Helpers for authenticating users in Remix applications.'
}
description={'Helpers for authenticating users in Remix applications.'}
style={{ height: '100%' }}
/>
</div>
@@ -32,10 +32,7 @@ Pass `supabaseClient` from `@supabase/supabase-js` as a prop to the component.
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 supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
const App = () => <Auth supabaseClient={supabase} />
```
@@ -80,10 +77,7 @@ import { createClient } from '@supabase/supabase-js'
//highlight-next-line
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
const supabase = createClient(
'<INSERT PROJECT URL>',
'<INSERT PROJECT ANON API KEY>'
)
const supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
const App = () => (
<Auth
@@ -94,9 +88,11 @@ const App = () => (
)
```
:::info
<Admonition type="info">
Currently there is only one predefined theme available, but we plan to add more.
:::
</Admonition>
### Switch theme variations
@@ -106,10 +102,7 @@ Auth UI comes with two theme variations: `default` and `dark`. You can switch be
import { createClient } from '@supabase/supabase-js'
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
const supabase = createClient(
'<INSERT PROJECT URL>',
'<INSERT PROJECT ANON API KEY>'
)
const supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
const App = () => (
<Auth
@@ -131,10 +124,7 @@ Auth UI themes can be overridden using variable tokens. See the [list of variabl
import { createClient } from '@supabase/supabase-js'
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
const supabase = createClient(
'<INSERT PROJECT URL>',
'<INSERT PROJECT ANON API KEY>'
)
const supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
const App = () => (
<Auth
@@ -219,10 +209,7 @@ You can use custom CSS classes for the following elements:
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 supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
const App = () => (
<Auth
@@ -247,10 +234,7 @@ You can use custom CSS inline styles for the following elements:
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 supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
const App = () => (
<Auth
@@ -274,10 +258,7 @@ You can use custom labels with `localization.variables`. See the [list of labels
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 supabase = createClient('<INSERT PROJECT URL>', '<INSERT PROJECT ANON API KEY>')
const App = () => (
<Auth
@@ -13,14 +13,12 @@ This submodule provides convenience helpers for implementing user authentication
## Install the Next.js helper library
<Tabs
groupId="install"
defaultValue="npm"
values={[
{label: 'npm', value: 'npm'},
{label: 'Yarn', value: 'yarn'},
]}>
<TabItem value="npm">
scrollable
size="small"
type="underlined"
defaultActiveId="npm"
>
<TabPanel id="npm" label="npm">
```sh
npm install @supabase/auth-helpers-nextjs
@@ -37,8 +35,8 @@ Additionally, install the **React Auth Helpers** for components and hooks that c
npm install @supabase/auth-helpers-react
```
</TabItem>
<TabItem value="yarn">
</TabPanel>
<TabPanel id="yarn" label="Yarn">
```sh
yarn add @supabase/auth-helpers-nextjs
@@ -55,7 +53,7 @@ Additionally, install the **React Auth Helpers** for components and hooks that c
yarn add @supabase/auth-helpers-react
```
</TabItem>
</TabPanel>
</Tabs>
## Set up environment variables
@@ -70,12 +68,12 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
## Basic Setup
<Tabs
defaultValue="js"
values={[
{label: 'JavaScript', value: 'js'},
{label: 'TypeScript', value: 'ts'},
]}>
<TabItem value="js">
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
Wrap your `pages/_app.js` component with the `SessionContextProvider` component:
@@ -99,8 +97,8 @@ function MyApp({ Component, pageProps }) {
}
```
</TabItem>
<TabItem value="ts">
</TabPanel>
<TabPanel id="ts" label="TypeScript">
Wrap your `pages/_app.tsx` component with the `SessionContextProvider` component:
@@ -130,7 +128,7 @@ function MyApp({
}
```
</TabItem>
</TabPanel>
</Tabs>
You can now determine if a user is authenticated by checking that the `user` object returned by the `useUser()` hook is defined.
@@ -272,13 +270,7 @@ Both `withApiAuth` and `withPageAuth` return a supabase client that you can use
```js
import { User, withPageAuth } from '@supabase/auth-helpers-nextjs'
export default function ProtectedPage({
user,
data,
}: {
user: User,
data: any,
}) {
export default function ProtectedPage({ user, data }: { user: User, data: any }) {
return (
<>
<div>Protected content for {user.email}</div>
@@ -300,18 +292,12 @@ export const getServerSideProps = withPageAuth({
## Server-side data fetching to OAuth APIs using `provider_token`
When using third-party auth providers, sessions are initiated with an additional `provider_token` field which is persisted as an HTTPOnly cookie upon logging in to enabled usage on the server side. The `provider_token` can be used to make API requests to the OAuth provider's API endpoints on behalf of the logged-in user. In the following example, we fetch the user's full profile from the third-party API during SSR using their id and auth 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.
```js
import { User, withPageAuth } from '@supabase/auth-helpers-nextjs'
export default function ProtectedPage({
user,
allRepos,
}: {
user: User,
allRepos: any,
}) {
export default function ProtectedPage({ user, allRepos }: { user: User, allRepos: any }) {
return (
<>
<div>Protected content for {user.email}</div>
@@ -342,15 +328,12 @@ export const getServerSideProps = withPageAuth({
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}`,
},
}
)
await fetch(`https://api.github.com/search/repositories?q=user:${userId}`, {
method: 'GET',
headers: {
Authorization: `token ${provider_token}`,
},
})
).json()
return { props: { allRepos, user } }
@@ -0,0 +1,879 @@
---
id: remix
title: Supabase Auth with Remix
description: Authentication helpers for loaders and actions in Remix.
sidebar_label: 'Remix'
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
This submodule provides convenience helpers for implementing user authentication in Remix applications.
## Install the Remix helper library
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="npm"
>
<TabPanel id="npm" label="npm">
```sh
npm install @supabase/auth-helpers-remix
```
This library supports the following tooling versions:
- Remix: `>=1.7.2`
</TabPanel>
<TabPanel id="yarn" label="Yarn">
```sh
yarn add @supabase/auth-helpers-remix
```
This library supports the following tooling versions:
- Remix: `>=1.7.2`
</TabPanel>
</Tabs>
## Set up environment variables
Retrieve your project URL and anon key in your project's [API settings](https://app.supabase.com/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 title=".env"
SUPABASE_URL=YOUR_SUPABASE_URL
SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
```
## Loader
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
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_ANON_KEY`, and a `Request` and `Response`.
```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_ANON_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,
}
)
}
```
> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function.
</TabPanel>
<TabPanel id="ts" label="TypeScript">
Loader functions run on the server immediately before the component is rendered. You can create an authenticated Supabase client by calling the `createServerClient` function and passing it your `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and a `Request` and `Response`.
```jsx
import { LoaderFunction, json } from '@remix-run/node' // change this import to whatever runtime you are using
import { createServerClient } from '@supabase/auth-helpers-remix'
export const loader: LoaderFunction = async ({ request }: { request: Request }) => {
const response = new Response()
const supabaseClient = createServerClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY,
{ request, response }
)
const { data } = await supabaseClient.from('test').select('*')
return json(
{ data },
{
headers: response.headers,
}
)
}
```
> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function.
</TabPanel>
</Tabs>
## Action
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
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_ANON_KEY`, and a `Request` and `Response`.
```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_ANON_KEY,
{ request, response }
)
const { data } = await supabaseClient.from('test').select('*')
return json(
{ data },
{
headers: response.headers,
}
)
}
```
> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function.
</TabPanel>
<TabPanel id="ts" label="TypeScript">
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_ANON_KEY`, and a `Request` and `Response`.
```jsx
import { ActionFunction, json } from '@remix-run/node' // change this import to whatever runtime you are using
import { createServerClient } from '@supabase/auth-helpers-remix'
export const action: ActionFunction = async ({ request }: { request: Request }) => {
const response = new Response()
const supabaseClient = createServerClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY,
{ request, response }
)
const { data } = await supabaseClient.from('test').select('*')
return json(
{ data },
{
headers: response.headers,
}
)
}
```
> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function.
</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.
```jsx
const user = session?.user
```
> This is the recommended way for accessing the logged in user. There is also a `getUser()` function but this does not refresh the session if it has expired.
## Client-side
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
In order to use the Supabase client in the browser - fetching data in `useEffect` or subscribing to realtime events - we need to do a little more plumbing. Remix does not include a way to make environment variables available to the browser, so we need to pipe them through from a `loader` function in our `root.jsx` route and attach them to the `window`.
```jsx title="app/root.jsx"
export const loader = () => {
const { SUPABASE_URL, SUPABASE_ANON_KEY } = process.env
return json({
env: {
SUPABASE_URL,
SUPABASE_ANON_KEY,
},
})
}
```
> These may not be stored in `process.env` for environments other than Node.
Next, we call the `useLoaderData` hook in our component to get the `env` object.
```jsx title="app/root.jsx"
const { env } = useLoaderData()
```
And then, add a `<script>` tag to attach these environment variables to the `window`. This should be placed immediately before the `<Scripts />` component in `app/root.jsx`
```jsx title="app/root.jsx"
<script
dangerouslySetInnerHTML={{
__html: `window.env = ${JSON.stringify(env)}`,
}}
/>
```
Full example for Node:
```jsx title="app/root.jsx"
import { json } from '@remix-run/node' // change this import to whatever runtime you are using
import {
Form,
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useLoaderData,
} from '@remix-run/react'
import { createBrowserClient, createServerClient } from '@supabase/auth-helpers-remix'
export const meta = () => ({
charset: 'utf-8',
title: 'New Remix App',
viewport: 'width=device-width,initial-scale=1',
})
export const loader = () => {
const { SUPABASE_URL, SUPABASE_ANON_KEY } = process.env
return json({
env: {
SUPABASE_URL,
SUPABASE_ANON_KEY,
},
})
}
export default function App() {
const { env } = useLoaderData()
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
<Outlet />
<ScrollRestoration />
<script
dangerouslySetInnerHTML={{
__html: `window.env = ${JSON.stringify(env)}`,
}}
/>
<Scripts />
<LiveReload />
</body>
</html>
)
}
```
Now we can call `createBrowserClient` in our components to fetch data client-side, or subscribe to realtime events - changes in the database.
</TabPanel>
<TabPanel id="ts" label="TypeScript">
In order to use the Supabase client in the browser - fetching data in `useEffect` or subscribing to realtime events - we need to do a little more plumbing. Remix does not include a way to make environment variables available to the browser, so we need to pipe them through from a `loader` function in our `root.tsx` route and attach them to the `window`.
```jsx title="app/root.tsx"
export const loader = () => {
const { SUPABASE_URL, SUPABASE_ANON_KEY } = process.env
return json({
env: {
SUPABASE_URL,
SUPABASE_ANON_KEY,
},
})
}
```
> These may not be stored in `process.env` for environments other than Node.
Next, we call the `useLoaderData` hook in our component to get the `env` object.
```jsx title="app/root.tsx"
const { env } = useLoaderData()
```
And then, add a `<script>` tag to attach these environment variables to the `window`. This should be placed immediately before the `<Scripts />` component in `app/root.tsx`
```jsx title="app/root.tsx"
<script
dangerouslySetInnerHTML={{
__html: `window.env = ${JSON.stringify(env)}`,
}}
/>
```
Full example for Node:
```jsx title="app/root.tsx"
import { json, MetaFunction, LoaderFunction } from '@remix-run/node' // change this import to whatever runtime you are using
import {
Form,
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useLoaderData,
} from '@remix-run/react'
import { createBrowserClient, createServerClient } from '@supabase/auth-helpers-remix'
export const meta: MetaFunction = () => ({
charset: 'utf-8',
title: 'New Remix App',
viewport: 'width=device-width,initial-scale=1',
})
export const loader: LoaderFunction = () => {
const { SUPABASE_URL, SUPABASE_ANON_KEY } = process.env
return json({
env: {
SUPABASE_URL,
SUPABASE_ANON_KEY,
},
})
}
export default function App() {
const { env } = useLoaderData()
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
<Outlet />
<ScrollRestoration />
<script
dangerouslySetInnerHTML={{
__html: `window.env = ${JSON.stringify(env)}`,
}}
/>
<Scripts />
<LiveReload />
</body>
</html>
)
}
```
Now we can call `createBrowserClient` in our components to fetch data client-side, or subscribe to realtime events - changes in the database.
</TabPanel>
</Tabs>
## Authentication
Now that authentication is based on cookies, users can sign in and out server-side with actions.
Given this Remix `<Form />` component.
```jsx
<Form method="post">
<input type="text" name="email" />
<input type="password" name="password" />
<button type="submit">Go!</button>
</Form>
```
### Signing Up
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
Any of the [supported authentication strategies from `supabase-js`](https://supabase.com/docs/reference/javascript/auth-signup) will work server-side. This is how you would handle simple `email` and `password` auth.
```jsx
export const action = async ({ request }) => {
const { email, password } = Object.fromEntries(await request.formData())
const response = new Response()
const supabaseClient = createServerClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY,
{ request, response }
)
const { data, error } = await supabaseClient.auth.signUp({
email,
password,
})
// in order for the set-cookie header to be set,
// headers must be returned as part of the loader response
return json(
{ data, error },
{
headers: response.headers,
}
)
}
```
</TabPanel>
<TabPanel id="ts" label="TypeScript">
Any of the [supported authentication strategies from `supabase-js`](https://supabase.com/docs/reference/javascript/auth-signup) will work server-side. This is how you would handle simple `email` and `password` auth.
```jsx
export const action: ActionFunction = async ({
request
}: {
request: Request;
}) => {
const { email, password } = Object.fromEntries(await request.formData());
const response = new Response();
const supabaseClient = createServerClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!,
{ request, response }
);
const { data, error } = await supabaseClient.auth.signUp({
email: String(registerEmail),
password: String(registerPassword)
});
// in order for the set-cookie header to be set,
// headers must be returned as part of the loader response
return json(
{ data, error },
{
headers: response.headers
}
);
};
```
</TabPanel>
</Tabs>
### Login
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
Any of the [supported authentication strategies from `supabase-js`](https://supabase.com/docs/reference/javascript/auth-signinwithpassword) will work server-side. This is how you would handle simple `email` and `password` auth.
```jsx
export const action = async ({ request }) => {
const { email, password } = Object.fromEntries(await request.formData())
const response = new Response()
const supabaseClient = createServerClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY,
{ request, response }
)
const { data, error } = await supabaseClient.auth.signInWithPassword({
email: String(loginEmail),
password: String(loginPassword),
})
// in order for the set-cookie header to be set,
// headers must be returned as part of the loader response
return json(
{ data, error },
{
headers: response.headers,
}
)
}
```
</TabPanel>
<TabPanel id="ts" label="TypeScript">
Any of the [supported authentication strategies from `supabase-js`](https://supabase.com/docs/reference/javascript/auth-signinwithpassword) will work server-side. This is how you would handle simple `email` and `password` auth.
```jsx
export const action: ActionFunction = async ({
request
}: {
request: Request;
}) => {
const { email, password } = Object.fromEntries(await request.formData());
const response = new Response();
const supabaseClient = createServerClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!,
{ request, response }
);
const { data, error } = await supabaseClient.auth.signInWithPassword({
email: String(loginEmail),
password: String(loginPassword)
});
// in order for the set-cookie header to be set,
// headers must be returned as part of the loader response
return json(
{ data, error },
{
headers: response.headers
}
);
};
```
</TabPanel>
</Tabs>
### Logout
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```jsx
export const action = async ({ request }) => {
const { email, password } = Object.fromEntries(await request.formData())
const response = new Response()
const supabaseClient = createServerClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY,
{ request, response }
)
const { error } = await supabaseClient.auth.signOut()
// in order for the set-cookie header to be set,
// headers must be returned as part of the loader response
return json(
{ error },
{
headers: response.headers,
}
)
}
```
</TabPanel>
<TabPanel id="ts" label="TypeScript">
```jsx
export const action: ActionFunction = async ({
request
}: {
request: Request;
}) => {
const { email, password } = Object.fromEntries(await request.formData());
const response = new Response();
const supabaseClient = createServerClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!,
{ request, response }
);
const { error } = await supabaseClient.auth.signOut();
// in order for the set-cookie header to be set,
// headers must be returned as part of the loader response
return json(
{ error },
{
headers: response.headers
}
);
};
```
</TabPanel>
</Tabs>
## Subscribe to realtime events
```jsx
import { createBrowserClient } from '@supabase/auth-helpers-remix'
import { useState, useEffect } from 'react'
export default function SubscribeToRealtime() {
const [data, setData] = useState([])
useEffect(() => {
const supabaseClient = createBrowserClient(
window.env.SUPABASE_URL,
window.env.SUPABASE_ANON_KEY
)
const channel = supabaseClient
.channel('test')
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'test' }, (payload) => {
setData((data) => [...data, payload.new])
})
.subscribe()
return () => {
supabaseClient.removeChannel(channel)
}
}, [session])
return <pre>{JSON.stringify({ data }, null, 2)}</pre>
}
```
> Note: `window.env` is not automatically populated by Remix. Check out the "Client-side" instructions above to configure this.
In this example we are listening to `INSERT` events on the `test` table. Anytime new rows are added to Supabase's `test` table, our UI will automatically update new data.
## Merge server and client state on realtime events
```jsx
import { json, LoaderFunction } from '@remix-run/node';
import { useLoaderData, useNavigate } from '@remix-run/react';
import {
createServerClient,
createBrowserClient
} from '@supabase/auth-helpers-remix';
import { useEffect } from 'react';
import { Database } from '../../db_types';
// this route demonstrates how to subscribe to realtime updates
// and synchronize data between server and client
export const loader: LoaderFunction = async ({
request
}: {
request: Request;
}) => {
const response = new Response();
const supabaseClient = createServerClient<Database>(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!,
{ request, response }
);
const {
data: { session }
} = await supabaseClient.auth.getSession();
const { data, error } = await supabaseClient.from('test').select('*');
if (error) {
throw error;
}
// in order for the set-cookie header to be set,
// headers must be returned as part of the loader response
return json(
{ data, session },
{
headers: response.headers
}
);
};
export default function SubscribeToRealtime() {
const { data, session } = useLoaderData();
const navigate = useNavigate();
useEffect(() => {
// Note: window.env is not automatically populated by Remix
// Check out the [example in this repo](../root.tsx) or
// [Remix docs](https://remix.run/docs/en/v1/guides/envvars#browser-environment-variables) for more info
const supabaseClient = createBrowserClient<Database>(
window.env.SUPABASE_URL,
window.env.SUPABASE_ANON_KEY
);
// make sure you have enabled `Replication` for your table to receive realtime events
// https://supabase.com/docs/guides/database/replication
const channel = supabaseClient
.channel('test')
.on(
'postgres_changes',
{ event: '*', schema: 'public', table: 'test' },
(payload: any) => {
// you could manually merge the `payload` with `data` here
// the `navigate` trick below causes all active loaders to be called again
// this handles inserts, updates and deletes, keeping everything in sync
// which feels more remix-y than manually merging state
// https://sergiodxa.com/articles/automatic-revalidation-in-remix
navigate('.', { replace: true });
}
)
.subscribe();
return () => {
supabaseClient.removeChannel(channel);
};
}, [session]);
return (
<div style={{ fontFamily: 'system-ui, sans-serif', lineHeight: '1.4' }}>
<pre>{JSON.stringify({ data }, null, 2)}</pre>
</div>
);
}
```
> Note: `window.env` is not automatically populated by Remix. Check out the "Client-side" instructions above to configure this.
## Usage with TypeScript
You can pass types that were [generated with the Supabase CLI](/docs/reference/javascript/typescript-support#generating-types) to the `createServerClient` or `createBrowserClient` functions to get enhanced type safety and auto completion:
### Server-side
```ts
import { createServerClient } from '@supabase/auth-helpers-remix'
import { Database } from '../../db_types'
export const loader = async ({ request }) => {
const response = new Response()
const supabaseClient = createServerClient<Database>(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY,
{ request, response }
)
}
```
### Client-side
```ts
import { createBrowserClient } from '@supabase/auth-helpers-remix'
import { Database } from '../../db_types'
const supabaseClient = createBrowserClient<Database>(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY
)
```
## Server-side data fetching to OAuth APIs using `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.
```js
import { json, LoaderFunction, redirect } from '@remix-run/node'; // change this import to whatever runtime you are using
import { useLoaderData } from '@remix-run/react';
import { createServerClient, User } from '@supabase/auth-helpers-remix';
import { Database } from '../../db_types';
export const loader: LoaderFunction = async ({
request
}: {
request: Request;
}) => {
const response = new Response();
const supabaseClient = createServerClient<Database>(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!,
{ request, response }
);
const {
data: { session }
} = await supabaseClient.auth.getSession();
if (!session) {
// there is no session, therefore, we are redirecting
// to the landing page. we still need to return
// response.headers to attach the set-cookie header
return redirect('/', {
headers: response.headers
});
}
// 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();
// in order for the set-cookie header to be set,
// headers must be returned as part of the loader response
return json(
{ user, allRepos },
{
headers: response.headers
}
);
};
export default function ProtectedPage() {
// by fetching the user in the loader, we ensure it is available
// for first SSR render - no flashing of incorrect state
const { user, allRepos } = useLoaderData<{ user: User; allRepos: any }>();
return <pre>{JSON.stringify({ user, allRepos }, null, 2)}</pre>;
}
```
@@ -15,28 +15,25 @@ This submodule provides convenience helpers for implementing user authentication
This library supports Node.js `^16.15.0`.
<Tabs
groupId="install"
defaultValue="npm"
values={[
{label: 'npm', value: 'npm'},
{label: 'Yarn', value: 'yarn'},
]}>
<TabItem value="npm">
scrollable
size="small"
type="underlined"
defaultActiveId="npm"
>
<TabPanel id="npm" label="npm">
```sh
npm install @supabase/auth-helpers-sveltekit
```
</TabItem>
<TabItem value="yarn">
</TabPanel>
<TabPanel id="yarn" label="Yarn">
```sh
yarn add @supabase/auth-helpers-sveltekit
```
</TabItem>
</TabPanel>
</Tabs>
## Getting Started
@@ -63,14 +60,10 @@ import { env } from '$env/dynamic/public'
// or use the static env
// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';
export const supabaseClient = createClient(
env.PUBLIC_SUPABASE_URL,
env.PUBLIC_SUPABASE_ANON_KEY,
{
persistSession: false,
autoRefreshToken: false,
}
)
export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY, {
persistSession: false,
autoRefreshToken: false,
})
setupSupabaseHelpers({
supabaseClient,
@@ -378,13 +371,9 @@ export const actions: Actions = {
const email = formData.get('email') as string
const password = formData.get('password') as string
const { data, error } = await supabaseClient.auth.api.signInWithEmail(
email,
password,
{
redirectTo: `${url.origin}/logging-in`,
}
)
const { data, error } = await supabaseClient.auth.api.signInWithEmail(email, password, {
redirectTo: `${url.origin}/logging-in`,
})
if (error || !data) {
if (error?.status === 400) {
@@ -470,13 +459,12 @@ The environment variable prefix is now `PUBLIC_` instead of `VITE_` (e.g., `VITE
### Set up the Supabase client {#migration-set-up-supabase-client}
<Tabs
groupId="version"
values={[
{label: '0.6.11 and below', value: 'older'},
{label: '0.7.0', value: 'latest'},
]}>
<TabItem value="older">
scrollable
size="small"
type="underlined"
defaultActiveId="older"
>
<TabPanel id="older" label="0.6.11 and below">
```js title="src/lib/db.ts"
import { createSupabaseClient } from '@supabase/auth-helpers-sveltekit';
@@ -489,9 +477,8 @@ const { supabaseClient } = createSupabaseClient(
export { supabaseClient };
```
</TabItem>
<TabItem value="latest">
</TabPanel>
<TabPanel id="latest" label="0.7.0">
```js title="src/lib/db.ts"
import { createClient } from '@supabase/supabase-js'
@@ -502,14 +489,10 @@ import { env } from '$env/dynamic/public'
// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';
export const supabaseClient = createClient(
env.PUBLIC_SUPABASE_URL,
env.PUBLIC_SUPABASE_ANON_KEY,
{
persistSession: false,
autoRefreshToken: false,
}
)
export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY, {
persistSession: false,
autoRefreshToken: false,
})
setupSupabaseHelpers({
supabaseClient,
@@ -519,19 +502,18 @@ setupSupabaseHelpers({
})
```
</TabItem>
</TabPanel>
</Tabs>
### Initialize the client {#migration-initialize-client}
<Tabs
groupId="version"
values={[
{label: '0.6.11 and below', value: 'older'},
{label: '0.7.0', value: 'latest'},
]}>
<TabItem value="older">
scrollable
size="small"
type="underlined"
defaultActiveId="older"
>
<TabPanel id="older" label="0.6.11 and below">
```html title="src/routes/__layout.svelte"
<script>
@@ -545,9 +527,8 @@ setupSupabaseHelpers({
</SupaAuthHelper>
```
</TabItem>
<TabItem value="latest">
</TabPanel>
<TabPanel id="latest" label="0.7.0">
The `@supabase/auth-helpers-svelte` library is no longer required as the `@supabase/auth-helpers-sveltekit` library handles all the client-side code.
@@ -569,19 +550,18 @@ The `@supabase/auth-helpers-svelte` library is no longer required as the `@supab
<slot />
```
</TabItem>
</TabPanel>
</Tabs>
### Set up hooks {#migration-set-up-hooks}
<Tabs
groupId="version"
values={[
{label: '0.6.11 and below', value: 'older'},
{label: '0.7.0', value: 'latest'},
]}>
<TabItem value="older">
scrollable
size="small"
type="underlined"
defaultActiveId="older"
>
<TabPanel id="older" label="0.6.11 and below">
```ts title="src/hooks.ts"
import { handleAuth } from '@supabase/auth-helpers-sveltekit'
@@ -600,9 +580,8 @@ export const getSession: GetSession = async (event) => {
}
```
</TabItem>
<TabItem value="latest">
</TabPanel>
<TabPanel id="latest" label="0.7.0">
```ts title="src/hooks.server.ts"
// make sure the supabase instance is initialized on the server
@@ -625,19 +604,18 @@ import { sequence } from '@sveltejs/kit/hooks'
export const handle = sequence(auth(), yourHandler)
```
</TabItem>
</TabPanel>
</Tabs>
### Typings {#migration-typings}
<Tabs
groupId="version"
values={[
{label: '0.6.11 and below', value: 'older'},
{label: '0.7.0', value: 'latest'},
]}>
<TabItem value="older">
scrollable
size="small"
type="underlined"
defaultActiveId="older"
>
<TabPanel id="older" label="0.6.11 and below">
```ts title="src/app.d.ts"
/// <reference types="@sveltejs/kit" />
@@ -660,9 +638,8 @@ declare namespace App {
}
```
</TabItem>
<TabItem value="latest">
</TabPanel>
<TabPanel id="latest" label="0.7.0">
```ts title="src/app.d.ts"
/// <reference types="@sveltejs/kit" />
@@ -684,19 +661,18 @@ declare namespace App {
}
```
</TabItem>
</TabPanel>
</Tabs>
### Check the user on the client
<Tabs
groupId="version"
values={[
{label: '0.6.11 and below', value: 'older'},
{label: '0.7.0', value: 'latest'},
]}>
<TabItem value="older">
scrollable
size="small"
type="underlined"
defaultActiveId="older"
>
<TabPanel id="older" label="0.6.11 and below">
```html title="src/routes/index.svelte"
<script>
@@ -711,9 +687,8 @@ declare namespace App {
{/if}
```
</TabItem>
<TabItem value="latest">
</TabPanel>
<TabPanel id="latest" label="0.7.0">
```html title="src/routes/+page.svelte"
<script>
@@ -728,26 +703,22 @@ declare namespace App {
{/if}
```
</TabItem>
</TabPanel>
</Tabs>
### withPageAuth
<Tabs
groupId="version"
values={[
{label: '0.6.11 and below', value: 'older'},
{label: '0.7.0', value: 'latest'},
]}>
<TabItem value="older">
scrollable
size="small"
type="underlined"
defaultActiveId="older"
>
<TabPanel id="older" label="0.6.11 and below">
```html title="src/routes/protected-route.svelte"
<script lang="ts" context="module">
import {
supabaseServerClient,
withPageAuth,
} from '@supabase/auth-helpers-sveltekit'
import { supabaseServerClient, withPageAuth } from '@supabase/auth-helpers-sveltekit'
import type { Load } from './__types/protected-page'
export const load: Load = async ({ session }) =>
@@ -757,9 +728,7 @@ declare namespace App {
user: session.user,
},
async () => {
const { data } = await supabaseServerClient(session.accessToken)
.from('test')
.select('*')
const { data } = await supabaseServerClient(session.accessToken).from('test').select('*')
return { props: { data, user: session.user } }
}
)
@@ -777,9 +746,8 @@ declare namespace App {
<pre>{JSON.stringify(user, null, 2)}</pre>
```
</TabItem>
<TabItem value="latest">
</TabPanel>
<TabPanel id="latest" label="0.7.0">
```html title="src/routes/protected-route/+page.svelte"
<script lang="ts">
@@ -801,39 +769,31 @@ import { withAuth } from '@supabase/auth-helpers-sveltekit'
import { redirect } from '@sveltejs/kit'
import type { PageLoad } from './$types'
export const load: PageLoad = withAuth(
async ({ session, getSupabaseClient }) => {
if (!session.user) {
throw redirect(303, '/')
}
const { data: tableData } = await getSupabaseClient()
.from('test')
.select('*')
return { tableData, user: session.user }
export const load: PageLoad = withAuth(async ({ session, getSupabaseClient }) => {
if (!session.user) {
throw redirect(303, '/')
}
)
const { data: tableData } = await getSupabaseClient().from('test').select('*')
return { tableData, user: session.user }
})
```
</TabItem>
</TabPanel>
</Tabs>
### withApiAuth
<Tabs
groupId="version"
values={[
{label: '0.6.11 and below', value: 'older'},
{label: '0.7.0', value: 'latest'},
]}>
<TabItem value="older">
scrollable
size="small"
type="underlined"
defaultActiveId="older"
>
<TabPanel id="older" label="0.6.11 and below">
```ts title="src/routes/api/protected-route.ts"
import {
supabaseServerClient,
withApiAuth,
} from '@supabase/auth-helpers-sveltekit'
import { supabaseServerClient, withApiAuth } from '@supabase/auth-helpers-sveltekit'
import type { RequestHandler } from './__types/protected-route'
interface TestTable {
@@ -848,9 +808,7 @@ interface GetOutput {
export const GET: RequestHandler<GetOutput> = async ({ locals, request }) =>
withApiAuth({ user: locals.user }, async () => {
// Run queries with RLS on the server
const { data } = await supabaseServerClient(request)
.from('test')
.select('*')
const { data } = await supabaseServerClient(request).from('test').select('*')
return {
status: 200,
@@ -859,9 +817,8 @@ export const GET: RequestHandler<GetOutput> = async ({ locals, request }) =>
})
```
</TabItem>
<TabItem value="latest">
</TabPanel>
<TabPanel id="latest" label="0.7.0">
```ts title="src/routes/api/protected-route/+server.ts"
import type { RequestHandler } from './$types';
@@ -886,7 +843,7 @@ export const GET: RequestHandler = withAuth(async ({ session, getSupabaseClient
);
```
</TabItem>
</TabPanel>
</Tabs>
## Additional Links
@@ -34,13 +34,13 @@ Keycloak OAuth consists of five broad steps:
- After you've added a new realm, you can retrieve the `issuer` from the "OpenID Endpoint Configuration" endpoint. The `issuer` will be used as the `Keycloak URL`.
- You can find this endpoint from the realm settings under the "General Tab" or visit [`http://localhost:8080/realms/my_realm_name/.well-known/openid-configuration`](http://localhost:8080/realms/my_realm_name/.well-known/openid-configuration)
![Add a Keycloak Realm.](/img/guides/auth-keycloak/keycloak-create-realm.png)
![Add a Keycloak Realm.](/docs/img/guides/auth-keycloak/keycloak-create-realm.png)
## Create a Keycloak Client
The "Client ID" of the created client will serve as the `client_id` when you make API calls to authenticate the user.
![Add a Keycloak client](/img/guides/auth-keycloak/keycloak-add-client.png)
![Add a Keycloak client](/docs/img/guides/auth-keycloak/keycloak-add-client.png)
## Client Settings
@@ -50,15 +50,15 @@ After you've created the client successfully, ensure that you set the following
2. The "Access Type" should be set to "confidential".
3. The "Valid Redirect URIs" should be set to: `https://<project-ref>.supabase.co/auth/v1/callback`.
![Obtain the client id, set the client protocol and access type](/img/guides/auth-keycloak/keycloak-client-id.png)
![Set redirect uri](/img/guides/auth-keycloak/keycloak-redirect-uri.png)
![Obtain the client id, set the client protocol and access type](/docs/img/guides/auth-keycloak/keycloak-client-id.png)
![Set redirect uri](/docs/img/guides/auth-keycloak/keycloak-redirect-uri.png)
## Obtain the Client Secret
This will serve as the `client_secret` when you make API calls to authenticate the user.
Under the "Credentials" tab, the `Secret` value will be used as the `client secret`.
![Obtain the client secret](/img/guides/auth-keycloak/keycloak-client-secret.png)
![Obtain the client secret](/docs/img/guides/auth-keycloak/keycloak-client-secret.png)
## Add login code to your client app
@@ -83,4 +83,4 @@ async function signout() {
## Resources
- You can find the keycloak openid endpoint configuration under the realm settings.
![Keycloak OpenID Endpoint Configuration](/img/guides/auth-keycloak/keycloak-openid-endpoint-config.png)
![Keycloak OpenID Endpoint Configuration](/docs/img/guides/auth-keycloak/keycloak-openid-endpoint-config.png)
@@ -22,7 +22,7 @@ Setting up LinkedIn logins for your application consists of 3 parts:
- Go to [LinkedIn Developer Dashboard](https://www.linkedin.com/developers/apps).
- Log in (if necessary.)
![LinkedIn Developer Portal](/img/guides/auth-linkedin/linkedin_developers_page.png)
![LinkedIn Developer Portal](/docs/img/guides/auth-linkedin/linkedin_developers_page.png)
## Find your callback URL
@@ -37,12 +37,7 @@ The next step requires a callback URL, which looks like this:
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
<video width="99%" muted playsInline controls="true">
<source
src="/docs/videos/api/api-url-and-key.mp4"
type="video/mp4"
muted
playsInline
/>
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
</video>
## Create a LinkedIn OAuth app
@@ -26,12 +26,12 @@ Setting up Magic Link logins for your Supabase application.
## Add login code to your client app
<Tabs
defaultValue="js"
groupId="guides/auth"
values={[{ label: 'JavaScript', value: 'js' }, { label: 'Dart', value: 'dart' }]}
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabItem value="js">
<TabPanel id="js" label="JavaScript">
When your user signs in, call [signInWithOtp()](/docs/reference/javascript/auth-signinwithotp) with their email address:
@@ -43,9 +43,8 @@ async function signInWithEmail() {
}
```
</TabItem>
<TabItem value="dart">
</TabPanel>
<TabPanel id="dart" label="Dart">
When your user signs in, call [signIn()](/docs/reference/dart/auth-signinwithotp) with their email address:
@@ -55,17 +54,16 @@ Future<void> signInWithEmail() async {
}
```
</TabItem>
</TabPanel>
</Tabs>
<Tabs
defaultValue="js"
groupId="guides/auth"
values={[{ label: 'JavaScript', value: 'js' }, { label: 'Dart', value: 'dart' }]}
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabItem value="js">
<TabPanel id="js" label="JavaScript">
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
@@ -75,9 +73,8 @@ async function signOut() {
}
```
</TabItem>
<TabItem value="dart">
</TabPanel>
<TabPanel id="dart" label="Dart">
When your user signs out, call [signOut()](/docs/reference/dart/auth-signout) to remove them from the browser session and any objects from localStorage:
@@ -87,8 +84,7 @@ Future<void> signOut() async {
}
```
</TabItem>
</TabPanel>
</Tabs>
## Resources
@@ -36,14 +36,14 @@ Start by logging into your MessageBird account and verify the mobile number you'
This is the number that will be receiving the SMS OTPs.
![Verify your own phone number](/img/guides/auth-messagebird/1.png)
![Verify your own phone number](/docs/img/guides/auth-messagebird/1.png)
![Get your API Keys](/img/guides/auth-messagebird/2.png)
![Get your API Keys](/docs/img/guides/auth-messagebird/2.png)
Navigate to the [dashboard settings](https://dashboard.messagebird.com/en/settings/sms) to set the default originator. The messagebird originator is the name or number from which the message is sent.
For more information, you can refer to the messagebird article on choosing an originator [here](https://support.messagebird.com/hc/en-us/articles/115002628665-Choosing-an-originator)
![Set the default originator](/img/guides/auth-messagebird/3.png)
![Set the default originator](/docs/img/guides/auth-messagebird/3.png)
You will need the following values to get started:
@@ -54,7 +54,7 @@ Now go to the Auth > Settings page in the Supabase dashboard (https://app.supaba
You should see an option to enable Phone Signup.
![Enable Phone Sign-Up](/img/guides/auth-twilio/7.png)
![Enable Phone Sign-Up](/docs/img/guides/auth-twilio/7.png)
Toggle it on, and copy the 2 values over from the messagebird dashboard. Click save.
@@ -82,12 +82,12 @@ In this use scenario we'll be using the user's mobile phone number as an alterna
Using supabase-js on the client you'll want to use the same `signUp` method that you'd use for email based sign ups, but with the `phone` param instead of the `email param`:
<Tabs
defaultValue="JS"
values={[
{label: 'JavaScript', value: 'JS'},
{label: 'HTTP', value: 'HTTP'},
]}>
<TabItem value="JS">
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```js
let { user, error } = await supabase.auth.signUp({
@@ -96,8 +96,8 @@ let { user, error } = await supabase.auth.signUp({
})
```
</TabItem>
<TabItem value="HTTP">
</TabPanel>
<TabPanel id="http" label="HTTP">
```bash
curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/signup' \
@@ -109,7 +109,7 @@ curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/signup' \
}'
```
</TabItem>
</TabPanel>
</Tabs>
The user will now receive an SMS with a 6-digit pin that you will need to receive from them within 60-seconds before they can login to their account.
@@ -117,12 +117,12 @@ The user will now receive an SMS with a 6-digit pin that you will need to receiv
You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyOTP`:
<Tabs
defaultValue="JS"
values={[
{label: 'JavaScript', value: 'JS'},
{label: 'HTTP', value: 'HTTP'},
]}>
<TabItem value="JS">
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```js
let { session, error } = await supabase.auth.verifyOTP({
@@ -131,8 +131,8 @@ let { session, error } = await supabase.auth.verifyOTP({
})
```
</TabItem>
<TabItem value="HTTP">
</TabPanel>
<TabPanel id="http" label="HTTP">
```bash
curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/verify' \
@@ -145,7 +145,7 @@ curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/verify' \
}'
```
</TabItem>
</TabPanel>
</Tabs>
If successful the user will now be logged in and you should receive a valid session like:
@@ -164,12 +164,12 @@ The access token can be sent in the Authorization header as a Bearer token for a
Also now that the mobile has been verified, the user can use the number and password to sign in without needing to verify their number each time:
<Tabs
defaultValue="JS"
values={[
{label: 'JavaScript', value: 'JS'},
{label: 'HTTP', value: 'HTTP'},
]}>
<TabItem value="JS">
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```js
let { user, error } = await supabase.auth.signInWithPassword({
@@ -178,8 +178,8 @@ let { user, error } = await supabase.auth.signInWithPassword({
})
```
</TabItem>
<TabItem value="HTTP">
</TabPanel>
<TabPanel id="http" label="HTTP">
```bash
curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/token?grant_type=password' \
@@ -191,7 +191,7 @@ curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/token?grant_type=
}'
```
</TabItem>
</TabPanel>
</Tabs>
### Using OTP as a passwordless sign-in mechanism
@@ -201,12 +201,12 @@ In this scenario you are granting your user's the ability to login to their acco
In javascript we can use the `signIn` method with a single parameter: `phone`
<Tabs
defaultValue="JS"
values={[
{label: 'JavaScript', value: 'JS'},
{label: 'HTTP', value: 'HTTP'},
]}>
<TabItem value="JS">
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```js
let { user, error } = await supabase.auth.signInWithOtp({
@@ -214,8 +214,8 @@ let { user, error } = await supabase.auth.signInWithOtp({
})
```
</TabItem>
<TabItem value="HTTP">
</TabPanel>
<TabPanel id="http" label="HTTP">
```bash
curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/otp' \
@@ -226,18 +226,18 @@ curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/otp' \
}'
```
</TabItem>
</TabPanel>
</Tabs>
The second step is the same as the previous section, you need to collect the 6-digit pin from the user and pass it along with their phone number to the verify method:
<Tabs
defaultValue="JS"
values={[
{label: 'JavaScript', value: 'JS'},
{label: 'HTTP', value: 'HTTP'},
]}>
<TabItem value="JS">
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```js
let { session, error } = await supabase.auth.verifyOTP({
@@ -246,8 +246,8 @@ let { session, error } = await supabase.auth.verifyOTP({
})
```
</TabItem>
<TabItem value="HTTP">
</TabPanel>
<TabPanel id="http" label="HTTP">
```bash
curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/verify' \
@@ -260,7 +260,7 @@ curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/verify' \
}'
```
</TabItem>
</TabPanel>
</Tabs>
and the response should also be the same as above:
+148 -144
View File
@@ -4,13 +4,15 @@ title: 'Multi-Factor Authentication'
description: Add an additional layer of security to your apps with Supabase Auth multi-factor authentication.
---
import { Mermaid } from 'mdx-mermaid/Mermaid';
import { Mermaid } from 'mdx-mermaid/Mermaid'
<Admonition type="note">
:::note
Multi-Factor Authentication is in early access preview only. Although we
believe it is production ready, APIs and some behavior may change based on
feedback we receive in the preview period.
:::
</Admonition>
Multi-factor authentication (MFA), sometimes called two-factor
authentication (2FA), adds an additional layer of security to your
@@ -25,9 +27,9 @@ MFA because they require the user to provide proof of
both of these:
- Something they know.
Password, or access to a social-login account.
Password, or access to a social-login account.
- Something they have.
Access to an authenticator app (a.k.a. TOTP), mobile phone or recovery code.
Access to an authenticator app (a.k.a. TOTP), mobile phone or recovery code.
## Overview
@@ -38,11 +40,12 @@ authenticator app in the control of users.
Applications using MFA require two important flows:
1. **Enrollment flow.**
This lets users set up and control MFA in your app.
This lets users set up and control MFA in your app.
2. **Authentication flow.**
This lets users sign in using any factors after the conventional login step.
This lets users sign in using any factors after the conventional login step.
Supabase Auth provides:
- **Enrollment API** - build rich user interfaces for adding and removing factors.
- **Challenge and Verify APIs** - securely verify that the user has access to a factor.
- **List Factors API** - build rich user interfaces for signing in with additional factors.
@@ -50,7 +53,8 @@ Supabase Auth provides:
Below is a flow chart illustrating how these APIs work together to enable MFA
features in your app.
<Mermaid chart={`
<Mermaid
chart={`
graph TD;
InitS((Setup flow)) --> SAAL1[/Session is AAL1/] --> Enroll[Enroll API] --> ShowQR[Show QR code] --> Scan([User: Scan QR code in authenticator]) --> Enter([User: Enter code]) --> Verify[Challenge + Verify API] --> Check{{Is code correct?}}
Check -->|Yes| AAL2[/Upgrade to AAL2/] --> Done((Done))
@@ -58,7 +62,8 @@ graph TD;
InitA((Login flow)) --> SignIn([User: Sign-in]) --> AAL1[/Upgrade to AAL1/] --> ListFactors[List Factors API]
ListFactors -->|1 or more factors| OpenAuth([User: Open authenticator]) --> Enter
ListFactors -->|0 factors| Setup[[Setup flow]]
`} />
`}
/>
These sets of APIs let you control the MFA experience that works for you. You
can create flows where MFA is optional, mandatory for all or only specific
@@ -74,12 +79,12 @@ standard measure about the assurance Supabase Auth has of the user's identity
for that particular session. There are two levels recognized today:
1. **Assurance Level 1: `aal1`**
Means that the user's identity was verified using a conventional login method
such as email+password, magic link, one-time password, phone auth or social
login.
Means that the user's identity was verified using a conventional login method
such as email+password, magic link, one-time password, phone auth or social
login.
2. **Assurance Level 2: `aal2`**
Means that the user's identity was additionally verified using at least one
second factor, such as a TOTP code.
Means that the user's identity was additionally verified using at least one
second factor, such as a TOTP code.
This assurance level is encoded in the `aal` claim in the JWT associated with
the user. By decoding this value you can create custom authorization rules in
@@ -91,17 +96,17 @@ for your application. JWTs without an `aal` claim are at the `aal1` level.
Adding MFA to your app involves these three steps:
1. **Add enrollment flow.**
You need to provide a UI within your app that your users will be able to set-up
MFA in. You can add this right after sign-up, or as part of a separate flow in
the settings portion of your app.
You need to provide a UI within your app that your users will be able to set-up
MFA in. You can add this right after sign-up, or as part of a separate flow in
the settings portion of your app.
2. **Add challenge step to login.**
If a user has set-up MFA, your app's login flow needs to present a challenge
screen to the user asking them to prove they have access to the additional
factor.
If a user has set-up MFA, your app's login flow needs to present a challenge
screen to the user asking them to prove they have access to the additional
factor.
3. **Enforce rules for MFA logins.**
Once your users have a way to enroll and log in with MFA, you need to enforce
authorization rules across your app: on the frontend, backend, API servers or
Row-Level Security policies.
Once your users have a way to enroll and log in with MFA, you need to enforce
authorization rules across your app: on the frontend, backend, API servers or
Row-Level Security policies.
### Add enrollment flow
@@ -109,12 +114,12 @@ An enrollment flow provides a UI for users to set up additional authentication f
Most applications add the enrollment flow in two places within their app:
1. Right after login or sign up.
This lets users quickly set up MFA immediately after they log in or create an
account. We recommend encouraging all users to set up MFA if that makes sense
for your application. Many applications offer this as an opt-in step in an
effort to reduce onboarding friction.
This lets users quickly set up MFA immediately after they log in or create an
account. We recommend encouraging all users to set up MFA if that makes sense
for your application. Many applications offer this as an opt-in step in an
effort to reduce onboarding friction.
2. From within a settings page.
Allows users to set up, disable or modify their MFA settings.
Allows users to set up, disable or modify their MFA settings.
We recommend building one generic flow that you can reuse in both cases with
minor modifications.
@@ -122,18 +127,18 @@ minor modifications.
Enrolling a factor for use with MFA takes three steps:
1. Call `supabase.auth.mfa.enroll()`.
This method returns a QR code and a secret. Display the QR
code to the user and ask them to scan it with their authenticator application.
If they are unable to scan the QR code, show the secret in plain text which
they can type or paste into their authenticator app.
This method returns a QR code and a secret. Display the QR
code to the user and ask them to scan it with their authenticator application.
If they are unable to scan the QR code, show the secret in plain text which
they can type or paste into their authenticator app.
2. Calling the `supabase.auth.mfa.challenge()` API.
This prepares Supabase Auth to accept a verification code from the user
and returns a challenge ID.
This prepares Supabase Auth to accept a verification code from the user
and returns a challenge ID.
3. Calling the `supabase.auth.mfa.verify()` API.
This verifies that the user has indeed added the secret from step (1) into
their app and is working correctly. If the verification succeeds, the factor
immediately becomes active for the user account. If not, you should repeat
steps 2 and 3.
This verifies that the user has indeed added the secret from step (1) into
their app and is working correctly. If the verification succeeds, the factor
immediately becomes active for the user account. If not, you should repeat
steps 2 and 3.
#### Example: React
@@ -142,15 +147,15 @@ the important pieces of the MFA enrollment flow.
- When the component appears on screen, the `supabase.auth.mfa.enroll()` API is
called once to start the process of enrolling a new factor for the current
user.
user.
- This API returns a QR code in the SVG format, which is shown on screen using
a normal `<img>` tag by encoding the SVG as a data URL.
- Once the user has scanned the QR code with their authenticator app, they
should enter the verification code within the `verifyCode` input field and
click on `Enable`.
click on `Enable`.
- A challenge is created using the `supabase.auth.mfa.challenge()` API and the
code from the user is submitted for verification using the
`supabase.auth.mfa.verify()` challenge.
`supabase.auth.mfa.verify()` challenge.
- `onEnabled` is a callback that notifies the other components that enrollment
has completed.
- `onCancelled` is a callback that notifies the other components that the user
@@ -169,56 +174,55 @@ export function EnrollMFA({
onEnrolled,
onCancelled,
}: {
onEnrolled: () => void;
onCancelled: () => void;
onEnrolled: () => void
onCancelled: () => void
}) {
const [factorId, setFactorId] = useState("");
const [qr, setQR] = useState(""); // holds the QR code image SVG
const [verifyCode, setVerifyCode] = useState(""); // contains the code entered by the user
const [error, setError] = useState(""); // holds an error message
const [factorId, setFactorId] = useState('')
const [qr, setQR] = useState('') // holds the QR code image SVG
const [verifyCode, setVerifyCode] = useState('') // contains the code entered by the user
const [error, setError] = useState('') // holds an error message
const onEnableClicked = () => {
setError("");
(async () => {
const challenge = await supabase.auth.mfa.challenge({ factorId });
setError('')
;(async () => {
const challenge = await supabase.auth.mfa.challenge({ factorId })
if (challenge.error) {
setError(challenge.error.message);
throw challenge.error;
setError(challenge.error.message)
throw challenge.error
}
const challengeId = challenge.data.id;
const challengeId = challenge.data.id
const verify = await supabase.auth.mfa.verify({
factorId,
challengeId,
code: verifyCode,
});
})
if (verify.error) {
setError(verify.error.message);
throw verify.error;
setError(verify.error.message)
throw verify.error
}
onEnrolled();
})();
};
onEnrolled()
})()
}
useEffect(() => {
(async () => {
;(async () => {
const { data, error } = await supabase.auth.mfa.enroll({
factorType: "totp",
});
factorType: 'totp',
})
if (error) {
throw error;
throw error
}
setFactorId(data.id);
setFactorId(data.id)
// Supabase Auth returns an SVG QR code which you can convert into a data
// URL that you can place in an <img> tag.
setQR(data.totp.qr_code);
})();
}, []);
setQR(data.totp.qr_code)
})()
}, [])
return (
<>
@@ -232,7 +236,7 @@ export function EnrollMFA({
<input type="button" value="Enable" onClick={onEnableClicked} />
<input type="button" value="Cancel" onClick={onCancelled} />
</>
);
)
}
```
@@ -248,16 +252,16 @@ in and is redirected back to your app, you should call this method to extract
the user's current and next authenticator assurance level (AAL).
Therefore if you receive a `currentLevel` which is `aal1` but a `nextLevel`
of `aal2`, the user should be given the option to go through MFA.
of `aal2`, the user should be given the option to go through MFA.
Below is a table that explains the combined meaning.
| Current Level | Next Level | Meaning |
| -------------:|:---------- |:------------------------------------------ |
| `aal1` | `aal1` | User does not have MFA enrolled. |
| `aal1` | `aal2` | User has an MFA factor enrolled but has not verified it. |
| `aal2` | `aal2` | User has verified their MFA factor. |
| `aal2` | `aal1` | User has disabled their MFA factor. (Stale JWT.) |
| Current Level | Next Level | Meaning |
| ------------: | :--------- | :------------------------------------------------------- |
| `aal1` | `aal1` | User does not have MFA enrolled. |
| `aal1` | `aal2` | User has an MFA factor enrolled but has not verified it. |
| `aal2` | `aal2` | User has verified their MFA factor. |
| `aal2` | `aal1` | User has disabled their MFA factor. (Stale JWT.) |
#### Example: React
@@ -272,44 +276,43 @@ illustrated in the `AppWithMFA` example below.
```tsx
function AppWithMFA() {
const [readyToShow, setReadyToShow] = useState(false);
const [showMFAScreen, setShowMFAScreen] = useState(false);
const [readyToShow, setReadyToShow] = useState(false)
const [showMFAScreen, setShowMFAScreen] = useState(false)
useEffect(() => {
(async () => {
;(async () => {
try {
const { data, error } =
await supabase.auth.mfa.getAuthenticatorAssuranceLevel();
const { data, error } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel()
if (error) {
throw error;
throw error
}
console.log(data);
console.log(data)
if (data.nextLevel === "aal2" && data.nextLevel !== data.currentLevel) {
setShowMFAScreen(true);
if (data.nextLevel === 'aal2' && data.nextLevel !== data.currentLevel) {
setShowMFAScreen(true)
}
} finally {
setReadyToShow(true);
setReadyToShow(true)
}
})();
}, []);
})()
}, [])
if (readyToShow) {
if (showMFAScreen) {
return <AuthMFA />;
return <AuthMFA />
}
return <App />;
return <App />
}
return <></>;
return <></>
}
```
- `supabase.auth.mfa.getAuthenticatorAssuranceLevel()` does return a promise.
Don't worry, this is a very fast method (microseconds) as it rarely uses the
network.
network.
- `readyToShow` only makes sure the AAL check completes before showing any
application UI to the user.
- If the current level can be upgraded to the next one, the MFA screen is
@@ -321,45 +324,44 @@ Below is the component that implements the challenge and verify logic.
```tsx
function AuthMFA() {
const [verifyCode, setVerifyCode] = useState("");
const [error, setError] = useState("");
const [verifyCode, setVerifyCode] = useState('')
const [error, setError] = useState('')
const onSubmitClicked = () => {
setError("");
(async () => {
const factors = await supabase.auth.mfa.listFactors();
setError('')
;(async () => {
const factors = await supabase.auth.mfa.listFactors()
if (factors.error) {
throw factors.error;
throw factors.error
}
const totpFactor = factors.data.totp[0];
const totpFactor = factors.data.totp[0]
if (!totpFactor) {
throw new Error("No TOTP factors found!");
throw new Error('No TOTP factors found!')
}
const factorId = totpFactor.id;
const factorId = totpFactor.id
const challenge = await supabase.auth.mfa.challenge({ factorId });
const challenge = await supabase.auth.mfa.challenge({ factorId })
if (challenge.error) {
setError(challenge.error.message);
throw challenge.error;
setError(challenge.error.message)
throw challenge.error
}
const challengeId = challenge.data.id;
const challengeId = challenge.data.id
const verify = await supabase.auth.mfa.verify({
factorId,
challengeId,
code: verifyCode,
});
})
if (verify.error) {
setError(verify.error.message);
throw verify.error;
setError(verify.error.message)
throw verify.error
}
})();
};
})()
}
return (
<>
@@ -372,22 +374,22 @@ function AuthMFA() {
/>
<input type="button" value="Submit" onClick={onSubmitClicked} />
</>
);
)
}
```
- You can extract the available MFA factors for the user by calling
`supabase.auth.mfa.listFactors()`. Don't worry this method is also very quick
and rarely uses the network.
and rarely uses the network.
- If `listFactors()` returns more than one factor (or of a different type) you
should present the user with a choice. For simplicity this is not shown in
the example.
the example.
- Each time the user presses the "Submit" button a new challenge is created for
the chosen factor (in this case the first one) and it is immediately
verified. Any errors are displayed to the user.
verified. Any errors are displayed to the user.
- On successful verification, the client library will refresh the session in
the background automatically and finally call the `onSuccess` callback, which
will show the authenticated `App` component on screen.
will show the authenticated `App` component on screen.
### Enforce rules for MFA logins
@@ -399,27 +401,29 @@ Depending on your application's needs, there are three ways you can choose to
enforce MFA.
1. **Enforce for all users (new and existing).**
Any user account will have to enroll MFA to continue using your app.
The application will not allow access without going through MFA first.
Any user account will have to enroll MFA to continue using your app.
The application will not allow access without going through MFA first.
2. **Enforce for new users only.**
Only new users will be forced to enroll MFA, while old users will be encouraged
to do so.
The application will not allow access for new users without going through MFA
first.
Only new users will be forced to enroll MFA, while old users will be encouraged
to do so.
The application will not allow access for new users without going through MFA
first.
3. **Enforce only for users that have opted-in.**
Users that want MFA can enroll in it and the application will not allow access
without going through MFA first.
Users that want MFA can enroll in it and the application will not allow access
without going through MFA first.
#### Database
Your app should sufficiently deny or allow access to tables or rows based on
the user's current and possible authenticator levels.
:::warning
<Admonition type="caution">
PostgreSQL has two types of policies: permissive and restrictive. This guide
uses restrictive policies. Make sure you don't omit the `as restrictive`
clause.
:::
</Admonition>
##### Enforce for all users (new and existing)
@@ -463,7 +467,7 @@ create policy "Policy name."
- The policy will accept both `aal1` and `aal2` for users with a `created_at`
timestamp prior to 12th December 2022 at 00:00 UTC, but will only accept
`aal2` for all other timestamps.
`aal2` for all other timestamps.
- The `<@` operator is PostgreSQL's ["contained in"
operator.](https://www.postgresql.org/docs/current/functions-array.html)
- `NULL` appears because some JWTs originating from prior to the introduction
@@ -505,12 +509,14 @@ create policy "Policy name."
### Server-Side Rendering
:::tip
<Admonition type="tip">
When using the Supabase JavaScript library in a server-side rendering context,
make sure you always create a new object for each request! This will prevent
you from accidentally rendering and serving content belonging to different
users.
:::
</Admonition>
It is possible to enforce MFA on the Server-Side Rendering level. However, this
can be tricky do to well.
@@ -524,11 +530,11 @@ However, encountering a different AAL level on the server may not actually be a
security problem. Consider these likely scenarios:
1. User signed-in with a conventional method but closed their tab on the MFA
flow.
flow.
2. User forgot a tab open for a very long time. (This happens more often than
you might imagine.)
you might imagine.)
3. User has lost their authenticator device and is confused about the next
steps.
steps.
We thus recommend you redirect users to a page where they can authenticate
using their additional factor, instead of rendering a HTTP 401 Unauthorized or
@@ -542,14 +548,14 @@ event that you have other APIs that you wish to protect, follow these general
guidelines:
1. **Use a good JWT verification and parsing library for your language.**
This will let you securely parse JWTs and extract their claims.
This will let you securely parse JWTs and extract their claims.
2. **Retrieve the `aal` claim from the JWT and compare its value according to
your needs.**
If you've encountered an AAL level that can be increased, ask the user to
continue the login process instead of logging them out.
your needs.**
If you've encountered an AAL level that can be increased, ask the user to
continue the login process instead of logging them out.
3. **Use the `https://<project-ref>.supabase.co/rest/v1/auth/factors` REST
endpoint to identify if the user has enrolled any MFA factors.**
Only `verified` factors should be acted upon.
endpoint to identify if the user has enrolled any MFA factors.**
Only `verified` factors should be acted upon.
## Frequently asked questions
@@ -602,11 +608,10 @@ You can use this PostgreSQL snippet in RLS policies, too:
json_query_path(auth.jwt(), '$.amr[0]')
```
- [`json_query_path(json,
path)`](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSON-PROCESSING-TABLE)
is a function that allows access to elements in a JSON object according to a
[SQL/JSON
path](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-SQLJSON-PATH).
- [`json_query_path(json, path)`](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSON-PROCESSING-TABLE)
is a function that allows access to elements in a JSON object according to a
[SQL/JSON
path](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-SQLJSON-PATH).
- `$.amr[0]` is a SQL/JSON path expression that fetches the most recent
authentication method in the JWT.
@@ -622,4 +627,3 @@ Currently recognized methods are:
- `mfa/totp` - a TOTP additional factor.
This list will expand in the future.
@@ -22,20 +22,20 @@ Setting up Notion logins for your application consists of 3 parts:
- Go to [developers.notion.com](https://developers.notion.com/).
- Click "View my integrations" and login.
![notion.so](/img/guides/auth-notion/notion.png)
![notion.so](/docs/img/guides/auth-notion/notion.png)
- Once logged in, go to [notion.so/my-integrations](https://notion.so/my-integrations) and create a new integration.
- When creating your integration, ensure that you select "Public integration" under "Integration type" and "Read user information including email addresses" under "Capabilities".
- You will need to add a redirect uri, see [Add the redirect uri](#add-the-redirect-uri)
- Once you've filled in the necessary fields, click "Submit" to finish creating the integration.
![notion.so](/img/guides/auth-notion/notion-developer.png)
![notion.so](/docs/img/guides/auth-notion/notion-developer.png)
## Add the redirect URI
- After selecting "Public integration", you should see an option to add "Redirect URIs".
![notion.so](/img/guides/auth-notion/notion-redirect-uri.png)
![notion.so](/docs/img/guides/auth-notion/notion-redirect-uri.png)
You can retrieve the redirect uri with the following steps:
@@ -48,19 +48,14 @@ You can retrieve the redirect uri with the following steps:
Your redirect uri should look like the following: `https://<project-ref>.supabase.co/auth/v1/callback`
<video width="99%" muted playsInline controls="true">
<source
src="/docs/videos/api/api-url-and-key.mp4"
type="video/mp4"
muted
playsInline
/>
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
</video>
## Add your Notion credentials into your Supabase Project
- Once you've created your notion integration, you should be able to retrieve the "OAuth client ID" and "OAuth client secret" from the "OAuth Domain and URIs" tab.
![notion.so](/img/guides/auth-notion/notion-creds.png)
![notion.so](/docs/img/guides/auth-notion/notion-creds.png)
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
- In the left sidebar, click the `Authentication` icon (near the top)
@@ -22,7 +22,7 @@ Setting up Slack logins for your application consists of 3 parts:
- Go to [api.slack.com](https://api.slack.com/apps).
- Click on `Your Apps` at the top right to log in.
![Slack Developer Portal.](/img/guides/auth-slack/slack-portal.png)
![Slack Developer Portal.](/docs/img/guides/auth-slack/slack-portal.png)
## Find your callback URL
@@ -37,12 +37,7 @@ The next step requires a callback URL, which looks like this:
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
<video width="99%" muted playsInline controls="true">
<source
src="/docs/videos/api/api-url-and-key.mp4"
type="video/mp4"
muted
playsInline
/>
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
</video>
## Create a Slack OAuth app
@@ -22,7 +22,7 @@ Setting up Spotify logins for your application consists of 3 parts:
- Log into [Spotify](https://spotify.com)
- Access the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard)
![Spotify Developer Portal.](/img/guides/auth-spotify/spotify-portal.png)
![Spotify Developer Portal.](/docs/img/guides/auth-spotify/spotify-portal.png)
## Find your callback URL
@@ -37,12 +37,7 @@ The next step requires a callback URL, which looks like this:
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
<video width="99%" muted playsInline controls="true">
<source
src="/docs/videos/api/api-url-and-key.mp4"
type="video/mp4"
muted
playsInline
/>
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
</video>
## Create a Spotify OAuth app
+54 -54
View File
@@ -47,18 +47,18 @@ Start by logging into your Twilio account and starting a new project: https://ww
Give your project a name and verify the mobile number you'll be using to test with. This is the number that will be receiving the SMS OTPs.
![Name your twilio project](/img/guides/auth-twilio/1.png)
![verify your own phone number](/img/guides/auth-twilio/2.png)
![Name your twilio project](/docs/img/guides/auth-twilio/1.png)
![verify your own phone number](/docs/img/guides/auth-twilio/2.png)
Select 'SMS', 'Identity & Verification', and 'With code' as options on the welcome form.
![Form Fields](/img/guides/auth-twilio/3.png)
![Form Fields](/docs/img/guides/auth-twilio/3.png)
When you're back on the [Twilio console screen](https://www.twilio.com/console), you need to scroll down and click 'Get a trial phone number' - this is the number that you'll be sending SMSs from.
![Get a trial phone number](/img/guides/auth-twilio/4.png)
![Get a trial phone number](/docs/img/guides/auth-twilio/4.png)
![Successful phone number](/img/guides/auth-twilio/5.png)
![Successful phone number](/docs/img/guides/auth-twilio/5.png)
You should now be able to see all three values you'll need to get started:
@@ -66,19 +66,19 @@ You should now be able to see all three values you'll need to get started:
- Auth Token
- Sender Phone Number
![All the credentials you'll need](/img/guides/auth-twilio/6.png)
![All the credentials you'll need](/docs/img/guides/auth-twilio/6.png)
Now go to the Auth > Settings page in the Supabase dashboard (https://app.supabase.com/project/YOUR-PROJECT-REF/auth/settings).
You should see an option to enable Phone Signup:
![Enable Phone Sign-Up](/img/guides/auth-twilio/7.png)
![Enable Phone Sign-Up](/docs/img/guides/auth-twilio/7.png)
Toggle it on, and copy the 3 values over from the twilio dashboard. Click save.
Note: for "Twilio Message Service SID" you can use the Sender Phone Number generated above.
![Plug in Twilio credentials](/img/guides/auth-twilio/8.png)
![Plug in Twilio credentials](/docs/img/guides/auth-twilio/8.png)
Now the backend should be setup, we can proceed to add our client-side code!
@@ -90,7 +90,7 @@ Go to Auth > Templates page in the Supabase dashboard (https://app.supabase.com/
Use the variable `.Code` in the template to display the OTP code. Here's an example in the SMS template.
![example in the SMS template](/img/guides/auth-twilio/9.png)
![example in the SMS template](/docs/img/guides/auth-twilio/9.png)
### Using OTP with password based logins
@@ -99,12 +99,12 @@ In this scenario we'll be using the user's mobile phone number and a correspondi
Using supabase-js on the client you'll want to use the same `signUp` method that you'd use for email based sign ups, but with the `phone` param instead of the `email param`:
<Tabs
defaultValue="JS"
values={[
{label: 'JavaScript', value: 'JS'},
{label: 'HTTP', value: 'HTTP'},
]}>
<TabItem value="JS">
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```js
let { user, error } = await supabase.auth.signUp({
@@ -113,8 +113,8 @@ let { user, error } = await supabase.auth.signUp({
})
```
</TabItem>
<TabItem value="HTTP">
</TabPanel>
<TabPanel id="http" label="HTTP">
```bash
curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/signup' \
@@ -126,7 +126,7 @@ curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/signup' \
}'
```
</TabItem>
</TabPanel>
</Tabs>
The user will now receive an SMS with a 6-digit pin that you will need to receive from them within 60-seconds before they can login to their account.
@@ -134,12 +134,12 @@ The user will now receive an SMS with a 6-digit pin that you will need to receiv
You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyOTP`:
<Tabs
defaultValue="JS"
values={[
{label: 'JavaScript', value: 'JS'},
{label: 'HTTP', value: 'HTTP'},
]}>
<TabItem value="JS">
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```js
let { session, error } = await supabase.auth.verifyOTP({
@@ -149,8 +149,8 @@ let { session, error } = await supabase.auth.verifyOTP({
})
```
</TabItem>
<TabItem value="HTTP">
</TabPanel>
<TabPanel id="http" label="HTTP">
```bash
curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/verify' \
@@ -163,7 +163,7 @@ curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/verify' \
}'
```
</TabItem>
</TabPanel>
</Tabs>
If successful the user will now be logged in and you should receive a valid session like:
@@ -182,12 +182,12 @@ The access token can be sent in the Authorization header as a Bearer token for a
Also now that the mobile has been verified, the user can use the number and password to sign in without needing to verify their number each time:
<Tabs
defaultValue="JS"
values={[
{label: 'JavaScript', value: 'JS'},
{label: 'HTTP', value: 'HTTP'},
]}>
<TabItem value="JS">
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```js
let { user, error } = await supabase.auth.signInWithPassword({
@@ -196,8 +196,8 @@ let { user, error } = await supabase.auth.signInWithPassword({
})
```
</TabItem>
<TabItem value="HTTP">
</TabPanel>
<TabPanel id="http" label="HTTP">
```bash
curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/token?grant_type=password' \
@@ -209,7 +209,7 @@ curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/token?grant_type=
}'
```
</TabItem>
</TabPanel>
</Tabs>
### Using OTP as a passwordless sign-in mechanism
@@ -219,12 +219,12 @@ In this scenario you are granting your user's the ability to login to their acco
In javascript we can use the `signIn` method with a single parameter: `phone`
<Tabs
defaultValue="JS"
values={[
{label: 'JavaScript', value: 'JS'},
{label: 'HTTP', value: 'HTTP'},
]}>
<TabItem value="JS">
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```js
let { user, error } = await supabase.auth.signInWithOtp({
@@ -232,8 +232,8 @@ let { user, error } = await supabase.auth.signInWithOtp({
})
```
</TabItem>
<TabItem value="HTTP">
</TabPanel>
<TabPanel id="http" label="HTTP">
```bash
curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/otp' \
@@ -244,18 +244,18 @@ curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/otp' \
}'
```
</TabItem>
</TabPanel>
</Tabs>
The second step is the same as the previous section, you need to collect the 6-digit pin from the user and pass it along with their phone number to the verify method:
<Tabs
defaultValue="JS"
values={[
{label: 'JavaScript', value: 'JS'},
{label: 'HTTP', value: 'HTTP'},
]}>
<TabItem value="JS">
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```js
let { session, error } = await supabase.auth.verifyOTP({
@@ -265,8 +265,8 @@ let { session, error } = await supabase.auth.verifyOTP({
})
```
</TabItem>
<TabItem value="HTTP">
</TabPanel>
<TabPanel id="http" label="HTTP">
```bash
curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/verify' \
@@ -279,7 +279,7 @@ curl -X POST 'https://cvwawazfelidkloqmbma.supabase.co/auth/v1/verify' \
}'
```
</TabItem>
</TabPanel>
</Tabs>
and the response should also be the same as above:
@@ -23,11 +23,11 @@ Setting up Twitch logins for your application consists of 3 parts:
- Click on `Log in with Twitch` at the top right to log in.
- If you have not already enabled 2-Factor Authentication for your Twitch Account, you will need to do that at [Twitch Security Settings](https://www.twitch.tv/settings/security) before you can continue.
![Twitch Developer Page](/img/guides/auth-twitch/twitch-developer-page.png)
![Twitch Developer Page](/docs/img/guides/auth-twitch/twitch-developer-page.png)
- Once logged in, go to the [Twitch Developer Console](https://dev.twitch.tv/console).
![Twitch Developer Console](/img/guides/auth-twitch/twitch-console.png)
![Twitch Developer Console](/docs/img/guides/auth-twitch/twitch-console.png)
## Find your callback URL
@@ -42,21 +42,16 @@ In the next step you require a callback URL, which looks like this:
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
<video width="99%" muted playsInline controls="true">
<source
src="/docs/videos/api/api-url-and-key.mp4"
type="video/mp4"
muted
playsInline
/>
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
</video>
## Create a Twitch Application
![Twitch Developer Console](/img/guides/auth-twitch/twitch-console.png)
![Twitch Developer Console](/docs/img/guides/auth-twitch/twitch-console.png)
- Click on `+ Register Your Application` at the top right.
![Register Application](/img/guides/auth-twitch/twitch-register-your-application.png)
![Register Application](/docs/img/guides/auth-twitch/twitch-register-your-application.png)
- Enter the name of your application.
- Type or paste your `OAuth Redirect URL` (the callback URL from the previous step.)
@@ -67,13 +62,13 @@ In the next step you require a callback URL, which looks like this:
- Click `Manage` at the right of your application entry in the list.
![Twitch Applications List](/img/guides/auth-twitch/twitch-applications-list.png)
![Twitch Applications List](/docs/img/guides/auth-twitch/twitch-applications-list.png)
- Copy your Client ID.
- Click `New Secret` to create a new Client Secret.
- Copy your Client Secret.
![Get Client ID and Secret](/img/guides/auth-twitch/twitch-get-keys.png)
![Get Client ID and Secret](/docs/img/guides/auth-twitch/twitch-get-keys.png)
## Add your Twitch credentials into your Supabase Project
@@ -22,7 +22,7 @@ Setting up Twitter logins for your application consists of 3 parts:
- Go to [developer.twitter.com](https://developer.twitter.com).
- Click on `Sign in` at the top right to log in.
![Twitter Developer Portal.](/img/guides/auth-twitter/twitter-portal.png)
![Twitter Developer Portal.](/docs/img/guides/auth-twitter/twitter-portal.png)
## Find your callback URL
@@ -37,12 +37,7 @@ The next step requires a callback URL, which looks like this:
- Now just add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
<video width="99%" muted playsInline controls="true">
<source
src="/docs/videos/api/api-url-and-key.mp4"
type="video/mp4"
muted
playsInline
/>
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
</video>
## Create a Twitter OAuth app
+45 -45
View File
@@ -73,12 +73,12 @@ In this use scenario we'll be using the user's mobile phone number as an alterna
Using supabase-js on the client you'll want to use the same `signUp` method that you'd use for email based sign ups, but with the `phone` param instead of the `email param`:
<Tabs
defaultValue="JS"
values={[
{label: 'JavaScript', value: 'JS'},
{label: 'HTTP', value: 'HTTP'},
]}>
<TabItem value="JS">
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```js
let { user, error } = await supabase.auth.signUp({
@@ -87,8 +87,8 @@ let { user, error } = await supabase.auth.signUp({
})
```
</TabItem>
<TabItem value="HTTP">
</TabPanel>
<TabPanel id="http" label="HTTP">
```bash
curl -X POST 'https://xxx.supabase.co/auth/v1/signup' \
@@ -100,7 +100,7 @@ curl -X POST 'https://xxx.supabase.co/auth/v1/signup' \
}'
```
</TabItem>
</TabPanel>
</Tabs>
The user will now receive an SMS with a 6-digit pin that you will need to receive from them within 60-seconds before they can login to their account.
@@ -108,12 +108,12 @@ The user will now receive an SMS with a 6-digit pin that you will need to receiv
You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyOTP`:
<Tabs
defaultValue="JS"
values={[
{label: 'JavaScript', value: 'JS'},
{label: 'HTTP', value: 'HTTP'},
]}>
<TabItem value="JS">
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```js
let { session, error } = await supabase.auth.verifyOTP({
@@ -122,8 +122,8 @@ let { session, error } = await supabase.auth.verifyOTP({
})
```
</TabItem>
<TabItem value="HTTP">
</TabPanel>
<TabPanel id="http" label="HTTP">
```bash
curl -X POST 'https://xxx.supabase.co/auth/v1/verify' \
@@ -136,7 +136,7 @@ curl -X POST 'https://xxx.supabase.co/auth/v1/verify' \
}'
```
</TabItem>
</TabPanel>
</Tabs>
If successful the user will now be logged in and you should receive a valid session like:
@@ -155,12 +155,12 @@ The access token can be sent in the Authorization header as a Bearer token for a
Also now that the mobile has been verified, the user can use the number and password to sign in without needing to verify their number each time:
<Tabs
defaultValue="JS"
values={[
{label: 'JavaScript', value: 'JS'},
{label: 'HTTP', value: 'HTTP'},
]}>
<TabItem value="JS">
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```js
let { user, error } = await supabase.auth.signInWithPassword({
@@ -169,8 +169,8 @@ let { user, error } = await supabase.auth.signInWithPassword({
})
```
</TabItem>
<TabItem value="HTTP">
</TabPanel>
<TabPanel id="http" label="HTTP">
```bash
curl -X POST 'https://xxx.supabase.co/auth/v1/token?grant_type=password' \
@@ -182,7 +182,7 @@ curl -X POST 'https://xxx.supabase.co/auth/v1/token?grant_type=password' \
}'
```
</TabItem>
</TabPanel>
</Tabs>
### Using OTP as a passwordless sign-in mechanism
@@ -192,12 +192,12 @@ In this scenario you are granting your user's the ability to login to their acco
In javascript we can use the `signIn` method with a single parameter: `phone`
<Tabs
defaultValue="JS"
values={[
{label: 'JavaScript', value: 'JS'},
{label: 'HTTP', value: 'HTTP'},
]}>
<TabItem value="JS">
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```js
let { user, error } = await supabase.auth.signInWithOtp({
@@ -205,8 +205,8 @@ let { user, error } = await supabase.auth.signInWithOtp({
})
```
</TabItem>
<TabItem value="HTTP">
</TabPanel>
<TabPanel id="http" label="HTTP">
```bash
curl -X POST 'https://xxx.supabase.co/auth/v1/otp' \
@@ -217,18 +217,18 @@ curl -X POST 'https://xxx.supabase.co/auth/v1/otp' \
}'
```
</TabItem>
</TabPanel>
</Tabs>
The second step is the same as the previous section, you need to collect the 6-digit pin from the user and pass it along with their phone number to the verify method:
<Tabs
defaultValue="JS"
values={[
{label: 'JavaScript', value: 'JS'},
{label: 'HTTP', value: 'HTTP'},
]}>
<TabItem value="JS">
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```js
let { session, error } = await supabase.auth.verifyOTP({
@@ -237,8 +237,8 @@ let { session, error } = await supabase.auth.verifyOTP({
})
```
</TabItem>
<TabItem value="HTTP">
</TabPanel>
<TabPanel id="http" label="HTTP">
```bash
curl -X POST 'https://xxx.supabase.co/auth/v1/verify' \
@@ -251,7 +251,7 @@ curl -X POST 'https://xxx.supabase.co/auth/v1/verify' \
}'
```
</TabItem>
</TabPanel>
</Tabs>
and the response should also be the same as above:
Loaded 100 of 491 files, more files were not shown because too many files have changed in this diff. Show more