Merge branch 'master' of github.com:supabase/supabase

This commit is contained in:
Terry Sutton committed 2022-12-14 09:15:32 -03:30
commit cdb66bee4f
192 files changed
+12515 -11969

No files matched your search

+6 -1
View File
@@ -1,10 +1,15 @@
name: Mirror Image
on:
workflow_call:
inputs:
version:
required: true
type: string
workflow_dispatch:
inputs:
version:
description: "Image tag"
description: 'Image tag'
required: true
type: string
+95 -26
View File
@@ -14,29 +14,39 @@ on:
type: string
jobs:
publish:
settings:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
image_version: ${{ steps.meta.outputs.version }}
steps:
- id: meta
uses: docker/metadata-action@v4
with:
images: |
supabase/studio
public.ecr.aws/supabase/studio
ghcr.io/supabase/studio
flavor: |
latest=false
tags: |
type=ref,event=tag
type=sha,prefix={{date 'YYYYMMDD'}},enable=${{ github.ref_type == 'branch' }}
type=raw,value=${{ inputs.version }},enable=${{ github.event_name != 'push' }}
type=sha,prefix={{date 'YYYYMMDD'}}-,enable=${{ github.event_name == 'push' && github.ref_type == 'branch' }}
type=raw,value=${{ inputs.version }},enable=${{ github.event_name == 'workflow_dispatch' }}
- uses: docker/setup-qemu-action@v2
release_x86:
needs: settings
runs-on: ubuntu-latest
timeout-minutes: 120
env:
arch: amd64
outputs:
image_digest: ${{ steps.build.outputs.digest }}
steps:
- id: meta
uses: docker/metadata-action@v4
with:
platforms: amd64,arm64
images: |
supabase/studio
tags: |
type=raw,value=${{ needs.settings.outputs.image_version }}_${{ env.arch }}
- uses: docker/setup-buildx-action@v2
@@ -46,27 +56,86 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to ECR
uses: docker/login-action@v2
with:
registry: public.ecr.aws
username: ${{ secrets.PROD_ACCESS_KEY_ID }}
password: ${{ secrets.PROD_SECRET_ACCESS_KEY }}
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v3
- id: build
uses: docker/build-push-action@v3
with:
push: true
context: '{{defaultContext}}'
file: studio/Dockerfile
target: production
platforms: linux/amd64,linux/arm64
platforms: linux/${{ env.arch }}
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
release_arm:
needs: settings
runs-on: arm-runner
timeout-minutes: 120
env:
arch: arm64
outputs:
image_digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v3
- id: meta
uses: docker/metadata-action@v4
with:
images: |
supabase/studio
tags: |
type=raw,value=${{ needs.settings.outputs.image_version }}_${{ env.arch }}
- uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- uses: docker/setup-buildx-action@v2
with:
driver: docker
driver-opts: |
image=moby/buildkit:master
network=host
- id: build
uses: docker/build-push-action@v3
with:
push: true
context: .
file: studio/Dockerfile
target: production
platforms: linux/${{ env.arch }}
tags: ${{ steps.meta.outputs.tags }}
no-cache: true
merge_manifest:
needs:
- settings
- release_x86
- release_arm
runs-on: ubuntu-latest
steps:
- uses: docker/setup-buildx-action@v2
- uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Merge multi-arch manifests
run: |
docker buildx imagetools create -t supabase/studio:${{ needs.settings.outputs.image_version }} \
supabase/studio@${{ needs.release_x86.outputs.image_digest }} \
supabase/studio@${{ needs.release_arm.outputs.image_digest }}
publish:
needs:
- settings
- merge_manifest
# Call workflow explicitly because events from actions cannot trigger more actions
uses: ./.github/workflows/mirror.yml
with:
version: ${{ needs.settings.outputs.image_version }}
secrets: inherit
+9
View File
@@ -8,6 +8,7 @@
- [Running turborepo](#running-turborepo)
- [Shared components](#shared-components)
- [Installing packages](#installing-packages)
- [New Supabase docs](#new-supabase-docs)
3. [Create a pull request](#create-a-pull-request)
- [Common tasks](#common-tasks)
@@ -106,6 +107,14 @@ For example:
You do not need to install `devDependencies` in each workspace. These can all be installed in the root package.
#### New Supabase docs
Following the changes to the [Supabase docs](https://supabase.com/blog/new-supabase-docs-built-with-nextjs) the following is needed to run the new docs locally:
- Inside of `apps/docs` create a `.env.local` file with the following: `NEXT_PUBLIC_NEW_DOCS=true`
Now when you run a local devlopment docs server you will see the new docs site.
---
## Create a pull request
@@ -102,6 +102,7 @@ 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: 'Login with Zoom', url: '/guides/auth/auth-zoom', items: [] },
{ name: 'Phone Auth with Twilio', url: '/guides/auth/auth-twilio', items: [] },
{ name: 'Phone Auth with Vonage', url: '/guides/auth/auth-vonage', items: [] },
{
@@ -265,6 +266,7 @@ export const menuItems: NavMenu = {
url: '/guides/platform/migrating-and-upgrading-projects',
items: [],
},
{ name: 'Network Restrictions', url: '/guides/platform/network-restrictions', 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: [] },
@@ -240,6 +240,12 @@ export const SocialLoginItems = [
url: '/guides/auth/social-login/auth-workos',
items: [],
},
{
name: 'Zoom',
icon: '/docs/img/icons/zoom-icon',
url: '/guides/auth/social-login/auth-zoom',
items: [],
},
]
export const PhoneLoginsItems = [
@@ -247,20 +253,20 @@ export const PhoneLoginsItems = [
name: 'MessageBird SMS Login',
icon: '/docs/img/icons/messagebird-icon',
linkDescription: 'Communication between businesses and their customers — across any channel.',
url: '/guides/auth/passwordless-login/phone-sms-otp-messagebird',
url: '/guides/auth/phone-login/messagebird',
items: [],
},
{
name: 'Twilio SMS Login',
icon: '/docs/img/icons/twilio-icon',
url: '/guides/auth/passwordless-login/phone-sms-otp-twilio',
url: '/guides/auth/phone-login/twilio',
linkDescription: 'Customer engagement platform used by hundreds of thousands of businesses.',
items: [],
},
{
name: 'Vonage SMS Login',
icon: '/docs/img/icons/vonage-icon',
url: '/guides/auth/passwordless-login/phone-sms-otp-vonage',
url: '/guides/auth/phone-login/vonage',
linkDescription:
'Vonage is a communication platform as a service (CPaaS) provider for consumers and businesses.',
items: [],
@@ -281,18 +287,11 @@ export const auth = {
url: undefined,
items: [
{ name: 'Email Login', url: '/guides/auth/auth-email', items: [] },
{ name: 'Magic Link Login', url: '/guides/auth/auth-magic-link', items: [] },
{
name: 'Passwordless Login',
url: '/guides/auth/passwordless-login',
items: [
{
name: 'Magic Link Login',
icon: '',
url: '/guides/auth/passwordless-login/email-otp-magic-link',
items: [],
},
...PhoneLoginsItems,
],
name: 'Phone Login',
url: '/guides/auth/phone-login',
items: [...PhoneLoginsItems],
},
{
name: 'Social Login',
@@ -475,6 +474,7 @@ export const platform = {
name: 'Platform Management',
url: undefined,
items: [
{ name: 'Access Control', url: '/guides/platform/access-control', items: [] },
{ name: 'Database Usage', url: '/guides/platform/database-usage', items: [] },
{ name: 'Logging', url: '/guides/platform/logs', items: [] },
{ name: 'Metrics', url: '/guides/platform/metrics', items: [] },
@@ -483,10 +483,29 @@ export const platform = {
url: '/guides/platform/migrating-and-upgrading-projects',
items: [],
},
{ name: 'Network Restrictions', url: '/guides/platform/network-restrictions', items: [] },
{ name: 'Performance Tuning', url: '/guides/platform/performance', items: [] },
{ name: 'Permissions', url: '/guides/platform/permissions', items: [] },
],
},
{
name: 'Single sign-on',
url: undefined,
items: [
{
name: 'Enable SSO for your organization',
url: '/guides/platform/sso',
items: [],
},
{ name: 'SSO with Azure AD', url: '/guides/platform/sso/azure', items: [] },
{
name: 'SSO with Google Workspace',
url: '/guides/platform/sso/gsuite',
items: [],
},
{ name: 'SSO with Okta', url: '/guides/platform/sso/okta', items: [] },
],
},
{
name: 'Go-live Checklist',
url: undefined,
@@ -500,7 +519,8 @@ export const resources = {
label: 'Resources',
url: '/guides/resources',
items: [
{ name: 'Examples', url: '/guides/resources/examples', items: [] },
// removing until the examples page is reworked
// { name: 'Examples', url: '/guides/resources/examples', items: [] },
{ name: 'Glossary', url: '/guides/resources/glossary', items: [] },
{
name: 'Migrate to Supabase',
@@ -512,8 +532,8 @@ export const resources = {
items: [],
},
{
name: 'Firebase Data',
url: '/guides/resources/migrating-to-supabase/firebase-data',
name: 'Firestore Data',
url: '/guides/resources/migrating-to-supabase/firestore-data',
items: [],
},
{
@@ -543,7 +563,7 @@ export const resources = {
},
{
name: 'Managing Environments',
url: '/guides/cli/managing-environments',
url: '/guides/resources/supabase-cli/managing-environments',
items: [],
},
],
+4
View File
@@ -24,6 +24,9 @@ import RefSubLayout from '~/layouts/ref/RefSubLayout'
// import { CH } from '@code-hike/mdx/components'
import RefHeaderSection from './reference/RefHeaderSection'
// Ref version specific
import CliGlobalFlagsHandler from '~/components/reference/enrichments/cli/CliGlobalFlagsHandler'
const components = {
Admonition,
Button,
@@ -64,6 +67,7 @@ const components = {
// CH,
code: (props: any) => <CodeBlock {...props} />,
RefHeaderSection: (props: any) => <RefHeaderSection {...props} />,
CliGlobalFlagsHandler: () => <CliGlobalFlagsHandler />,
}
export default components
@@ -1,3 +1,4 @@
import { IconChevronRight } from '~/../../packages/ui'
// @ts-expect-error
import spec from '~/../../spec/cli_v1_commands.yaml' assert { type: 'yml' }
import CodeBlock from '~/components/CodeBlock/CodeBlock'
@@ -80,13 +81,19 @@ const CliCommandSection = (props) => {
)} */}
{command.subcommands.length > 0 && (
<div className="">
<h3 className="text-sm text-scale-1200 mb-3">Available Commands</h3>
<div className="mb-3">
<h3 className="text-lg text-scale-1200 mb-3">Available Commands</h3>
<ul>
{command.subcommands.map((subcommand) => (
<li key={subcommand}>
<a href={`#${subcommand}`} className="text-scale-1200">
<CodeBlock language="bash">{subcommand}</CodeBlock>
<li key={subcommand} className="flex items-center gap-3">
<div className="text-scale-900">
<IconChevronRight size={14} strokeWidth={2} />
</div>
<a
href={`#${subcommand}`}
className="transition text-scale-1100 hover:text-brand-900"
>
$ {subcommand.replace(/-/g, ' ')}
</a>
</li>
))}
@@ -0,0 +1,30 @@
import RefSubLayout from '~/layouts/ref/RefSubLayout'
// @ts-expect-error
import spec from '~/../../spec/cli_v1_commands.yaml' assert { type: 'yaml' }
import Param from '~/components/Params'
import Options from '~/components/Options'
const CliGlobalFlagsHandler = () => {
return (
<RefSubLayout.EducationRow className="not-prose">
<RefSubLayout.Details>
<h3 className="text-lg text-scale-1200 mb-3">Flags</h3>
<ul className="">
{spec.flags.map((flag) => {
return (
<Param
{...flag}
isOptional={flag.required === undefined ? true : !flag.required}
></Param>
)
})}
</ul>
</RefSubLayout.Details>
<RefSubLayout.Examples></RefSubLayout.Examples>
</RefSubLayout.EducationRow>
)
}
export default CliGlobalFlagsHandler
+1 -1
View File
@@ -90,7 +90,7 @@
},
{
"text": "SupaSquad",
"url": "/docs/handbook/supasquad"
"url": "/supasquad"
},
{
"text": "DevTo",
+8
View File
@@ -0,0 +1,8 @@
---
title: Global Flags
description: 'Supabase CLI supports global flags for every command.'
---
Supabase CLI supports global flags for every command.
<CliGlobalFlagsHandler />
+2 -2
View File
@@ -291,8 +291,8 @@ const SiteRefLayout = ({ children }) => {
<Link href="https://supabase.com/docs/oss">
<a className="text-xs text-scale-800 hover:underline">Open Source</a>
</Link>
<Link href="https://supabase.com/docs/handbook/supasquad">
<a className="text-xs text-scale-800 hover:underline">Supasquad</a>
<Link href="https://supabase.com/supasquad">
<a className="text-xs text-scale-800 hover:underline">SupaSquad</a>
</Link>
</div>
</div>
+2 -2
View File
@@ -292,8 +292,8 @@ const SiteRefLayout = ({ children }) => {
<Link href="https://supabase.com/docs/oss">
<a className="text-xs text-scale-800 hover:underline">Open Source</a>
</Link>
<Link href="https://supabase.com/docs/handbook/supasquad">
<a className="text-xs text-scale-800 hover:underline">Supasquad</a>
<Link href="https://supabase.com/supasquad">
<a className="text-xs text-scale-800 hover:underline">SupaSquad</a>
</Link>
</div>
</div>
+16
View File
@@ -1,7 +1,9 @@
import { MDXProvider } from '@mdx-js/react'
import { NextSeo } from 'next-seo'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { FC, useEffect, useRef, useState } from 'react'
import { IconExternalLink } from 'ui'
import components from '~/components'
import { highlightSelectedTocItem } from '~/components/CustomHTMLElements/CustomHTMLElements.utils'
import GuidesTableOfContents from '~/components/GuidesTableOfContents'
@@ -31,6 +33,8 @@ const Layout: FC<Props> = (props) => {
const { asPath } = useRouter()
const page = getPageType(asPath)
const router = useRouter()
useEffect(() => {
if (hash && tocList.length > 0) {
highlightSelectedTocItem(hash as string)
@@ -96,6 +100,18 @@ const Layout: FC<Props> = (props) => {
)}
<div className="max-w-xs w-32 h-[1px] bg-gradient-to-r from-brand-800 to-brand-900 my-8"></div>
<MDXProvider components={components}>{props.children}</MDXProvider>
<div className="mt-16 not-prose">
<div>
<Link
href={`https://github.com/supabase/supabase/edit/master/apps/docs/pages${router.asPath}.mdx`}
passHref
>
<a className="text-sm transition flex items-center gap-1 text-scale-1000 hover:text-scale-1200">
Edit this page on GitHub <IconExternalLink size={14} strokeWidth={1.5} />
</a>
</Link>
</div>
</div>
</article>
</div>
{!props.hideToc && hasTableOfContents && !props.meta?.hide_table_of_contents && (
+8 -2
View File
@@ -34,7 +34,9 @@ type StickyHeader = {
type RefSubLayoutType = {}
interface IEducationRow {}
interface IEducationRow {
className?: string
}
interface IEducationSection {
id: string
title?: string
@@ -129,7 +131,11 @@ const Examples: FC<ISectionExamples> = (props) => {
}
const EducationRow: FC<IEducationRow> = (props) => {
return <div className="grid lg:grid-cols-2 gap-8 lg:gap-16">{props.children}</div>
return (
<div className={['grid lg:grid-cols-2 gap-8 lg:gap-16', props.className].join(' ')}>
{props.children}
</div>
)
}
const EducationSection: FC<IEducationSection> = ({ icon, hideTitle = false, ...props }) => {
-162
View File
@@ -1,162 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'architecture',
title: 'Architecture',
description: 'Supabase design and architecture',
}
Supabase is open source. We choose open source tools which are scalable and make them simple to use.
Supabase is not a 1-to-1 mapping of Firebase. While we are building many of the features that Firebase offers, we are not going about it the same way:
our technological choices are quite different; everything we use is open source; and wherever possible, we use and support existing tools rather than developing from scratch.
Most notably, we use Postgres rather than a NoSQL store. This choice was deliberate. We believe that no other database offers the functionality required to compete with Firebase,
while maintaining the scalability required to go beyond it.
## Architecture
Each Supabase project consists of several tools:
![Supabase Architecture](/docs/img/supabase-architecture.png)
### PostgreSQL (Database)
PostgreSQL is the core of Supabase. We do not abstract the PostgreSQL database — you can access it and use it with full privileges. We simply provide tools which makes PostgreSQL as easy to use as Firebase.
- Official Docs: [postgresql.org/docs](https://www.postgresql.org/docs/current/index.html)
- Source code: [github.com/postgres/postgres](https://github.com/postgres/postgres) (mirror)
- License: [PostgreSQL Licence](https://www.postgresql.org/about/licence/)
- Language: C
### Studio (Dashboard)
An open source Dashboard for managing your database and services.
- Official Docs: [Supabase docs](/docs)
- Source code: [github.com/supabase/supabase](https://github.com/supabase/supabase/tree/master/studio)
- License: [Apache 2](https://github.com/supabase/supabase/blob/master/LICENSE)
- Language: TypeScript
### GoTrue (Auth)
A JWT-based API for managing users and issuing access tokens. This integrates with PostgreSQL's Row Level Security and the API servers.
- Official Docs: [Supabase Auth reference docs](/docs/reference/auth)
- Source code: [github.com/supabase/gotrue](https://github.com/supabase/gotrue)
- License: [MIT](https://github.com/supabase/gotrue/blob/master/LICENSE)
- Language: Go
### PostgREST (API)
A standalone web server that turns your PostgreSQL database directly into a RESTful API.
We use this with our [pg_graphql](https://github.com/supabase/pg_graphql) extension to provide a GraphQL API.
- Official Docs: [postgrest.org](https://postgrest.org/)
- Source code: [github.com/PostgREST/postgrest](https://github.com/PostgREST/postgrest)
- License: [MIT](https://github.com/PostgREST/postgrest/blob/main/LICENSE)
- Language: Haskel
### Realtime (API & multiplayer)
A scalable websocket engine for managing user Presence, broadcasting messages, and streaming database changes.
- Official Docs: [Supabase Realtime docs](/docs/guides/realtime)
- Source code: [github.com/supabase/realtime](https://github.com/supabase/realtime)
- License: [Apache 2](https://github.com/supabase/realtime/blob/master/LICENSE)
- Language: Elixir
### Storage API (large file storage)
An S3-compatible object storage service that stores metadata in Postgres.
- Official Docs: [Supabase Storage reference docs](/docs/reference/storage)
- Source code: [github.com/supabase/storage-api](https://github.com/supabase/storage-api)
- License: [Apache 2.0](https://github.com/supabase/storage-api/blob/master/LICENSE)
- Language: NodeJS / TypeScript
### Deno (Edge Functions)
A modern runtime for JavaScript and TypeScript.
- Official Docs: [deno.land](https://deno.land/)
- Source code: [github.com/denoland/deno](https://github.com/denoland/deno)
- License: [MIT](https://github.com/denoland/deno/blob/main/LICENSE.md)
- Language: TypeScript / Rust
### postgres-meta (Database management)
A RESTful API for managing your Postgres. Fetch tables, add roles, and run queries.
- Official Docs: [supabase.github.io/postgres-meta](https://supabase.github.io/postgres-meta/)
- Source code: [github.com/supabase/postgres-meta](https://github.com/supabase/postgres-meta)
- License: [Apache 2.0](https://github.com/supabase/postgres-meta/blob/master/LICENSE)
- Language: NodeJS / TypeScript
### PgBouncer
A lightweight connection pooler for PostgreSQL. This is useful for connecting to Postgres when using Serverless functions.
- Official Docs: [pgbouncer.org](http://www.pgbouncer.org/)
- Source code: [pgbouncer/pgbouncer](https://github.com/pgbouncer/pgbouncer)
- License: [ISC](https://github.com/pgbouncer/pgbouncer/blob/master/COPYRIGHT)
- Language: C
### Kong (API Gateway)
A cloud-native API gateway, built on top of Nginx.
- Official Docs: [docs.konghq.com](https://docs.konghq.com/)
- Source code: [github.com/kong/kong](https://github.com/kong/kong)
- License: [Apache 2.0](https://github.com/Kong/kong/blob/master/LICENSE)
- Language: Lua
## Product Principles
It is our goal to provide an architecture that any large-scale company would design for themselves,
and then provide tooling around that architecture that is easy-to-use for indie-developers and small teams.
We use a series of principles to ensure that scalability and usability are never mutually exclusive:
### Everything works in isolation
Each system must work as a standalone tool with as few moving parts as possible.
The litmus test for this is: "Can a user run this product with nothing but a Postgres database?"
### Everything is integrated
Supabase is composable. Even though every product works in isolation, each product on the platform needs to 10x the other products.
For integration, each tool should expose an API and Webhooks.
### Everything is extensible
We're deliberate about adding a new tool, and prefer instead to extend an existing one.
This is the opposite of many cloud providers whose product offering expands into niche use-cases. We provide _primitives_ for developers, which allow them to achieve any goal.
Less, but better.
### Everything is portable
To avoid lock-in, we make it easy to migrate in and out. Our cloud offering is compatible with our self-hosted product.
We use existing standards to increase portability (like pg_dump an CSV files). If a new standard emerges which competes with a "Supabase" approach, we will deprecate the approach in favor of the standard.
This forces us compete on experience. We aim to be the best Postgres hosting service.
### Play the long game
We sacrifice short-term wins for long-term gains. For example, it is tempting to run a fork of Postgres with additional functionality which only our customers need.
Instead, we prefer to support efforts to upstream missing functionality so that the entire community benefits. This has the additional benefit of ensuring portability and longevity.
### Build for developers
"Developers" are a specific profile of user: they are _builders_.
When assessing impact as a function of effort, developers have a large efficiency due to the type of products and systems they can build.
As the profile of a developer changes over time, Supabase will continue to evolve the product to fit this evolving profile.
### Support existing tools
Supabase supports existing tools and communities wherever possible. Supabase is more like a "community of communities" - each tool typically has its own community
which we work with.
Open source is something we approach [collaboratively](https://supabase.com/blog/supabase-series-b#giving-back): we employ maintainers, sponsor projects, invest in businesses, and develop our own open source tools.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
-184
View File
@@ -1,184 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
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-changes).
### 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 />
## Storage
### File storage
Supabase Storage makes it simple to store and serve files. [Docs](/docs/guides/storage).
### Storage CDN
Cache large files using the Supabase CDN. [Docs](/docs/guides/storage-cdn).
### Image Transformations
Transform images on the fly. [Docs](/docs/guides/storage/image-transformations).
<br />
## Edge Functions
### Deno Edge Functions
Globally distributed TypeScript functions to execute custom business logic. [Docs](/docs/guides/functions).
<br />
## Project Management
### CLI
Use our CLI to develop your project locally and deploy to the Supabase Platform. [Docs](/docs/reference/cli).
### Management 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 Changes | `GA` |
| Realtime | Broadcast | `beta` |
| Realtime | Presence | `beta` |
| Storage | | `GA` |
| Storage | S3 Backend | `GA` |
| Storage | CDN | `GA` |
| Storage | Smart CDN | `beta` |
| Storage | Image Transformations | `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` |
| Management API | | `beta` |
| CLI | | `beta` |
| Client Library: JavaScript | | `GA` |
| Client Library: Dart | | `beta` |
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
-185
View File
@@ -1,185 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
import NewContent from '~/pages/new/auth.mdx'
import { useRouter } from 'next/router'
export const meta = {
id: 'auth',
title: 'Auth',
description: 'Use Supabase to Authenticate and Authorize your users.',
sidebar_label: 'Overview',
}
## Overview
There are two parts to every Auth system:
- **Authentication:** should this person be allowed in? If yes, who are they?
- **Authorization:** once they are in, what are they allowed to do?
Supabase Auth is designed to work either as a standalone product, or deeply integrated with the other Supabase products.
Postgres is at the heart of everything we do, and the Auth system follows this principle. We leverage Postgres' built-in Auth functionality wherever possible.
Here's a quick, 2 minute tour of the Auth features built-in to Supabase:
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/6ow_jW4epf8"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
## Authentication
You can authenticate your users in several ways:
- Email & password.
- Magic links (one-click logins).
- Social providers.
- Phone logins.
### Providers
We provide a suite of Providers and login methods, as well as [Auth helpers](/docs/guides/auth/auth-helpers/).
<div className="container" style={{ padding: 0 }}>
<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.](/docs/img/supabase-oauth-logins.png)
### Redirect URLs and wildcards
When using third-party providers, the [Supabase client library](/docs/reference/javascript/auth-signinwithoauth#sign-in-using-a-third-party-provider-with-redirect) redirects the user to the provider. When the third-party provider successfully authenticates the user, the provider redirects the user to the Supabase Auth callback URL where they are further redirected to the URL specified in the `redirectTo` parameter. This parameter defaults to the [`SITE_URL`](/docs/reference/auth/config#site_url). You can modify the `SITE_URL` or [add additional redirect URLs](https://app.supabase.com/project/_/auth/settings).
You can use wildcard match patterns to support preview URLs from providers like Netlify and Vercel. See the [full list of supported patterns](https://pkg.go.dev/github.com/gobwas/glob#Compile). Use [this tool](https://www.digitalocean.com/community/tools/glob?comments=true&glob=http%3A%2F%2Flocalhost%3A3000%2F%2A%2A&matches=false&tests=http%3A%2F%2Flocalhost%3A3000&tests=http%3A%2F%2Flocalhost%3A3000%2F&tests=http%3A%2F%2Flocalhost%3A3000%2F%3Ftest%3Dtest&tests=http%3A%2F%2Flocalhost%3A3000%2Ftest-test%3Ftest%3Dtest&tests=http%3A%2F%2Flocalhost%3A3000%2Ftest%2Ftest%3Ftest%3Dtest) to test your patterns.
> ⚠️ WARNING: While the "globstar" (`**`) is useful for local development and preview URLs, we recommend setting the exact redirect URL path for your site URL in production.
#### Netlify preview URLs
For deployments with Netlify, set the `SITE_URL` to your official site URL. Add the following additional redirect URLs for local development and deployment previews:
- `http://localhost:3000/**`
- `https://**--my_org.netlify.app/**`
#### Vercel preview URLs
For deployments with Vercel, set the `SITE_URL` to your official site URL. Add the following additional redirect URLs for local development and deployment previews:
- `http://localhost:3000/**`
- `https://*-username.vercel.app/**`
Vercel provides an environment variable for the URL of the deployment called `NEXT_PUBLIC_VERCEL_URL`. See the [Vercel docs](https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables) for more details. You can use this variable to dynamically redirect depending on the environment:
```js
const getURL = () => {
let url =
process?.env?.NEXT_PUBLIC_SITE_URL ?? // Set this to your site URL in production env.
process?.env?.NEXT_PUBLIC_VERCEL_URL ?? // Automatically set by Vercel.
'http://localhost:3000/';
// Make sure to include `https://` when not localhost.
url = url.includes('http') ? url : `https://${url}`;
// Make sure to including trailing `/`.
url = url.charAt(url.length - 1) === '/' ? url : `${url}/`;
return url;
};
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github'
options: {
redirectTo: getURL()
}
}
```
#### Mobile deep linking URIs
For mobile applications you can use deep linking URIs. For example for your `SITE_URL` you can specify something like `com.supabase://login-callback/` and for additional redirect URLs something like `com.supabase.staging://login-callback/` if needed.
## Authorization
When you need granular authorization rules, nothing beats PostgreSQL's Row Level Security (RLS).
Policies are PostgreSQL's rule engine. They are incredibly powerful and flexible, allowing you to write complex SQL rules which fit your unique business needs.
Get started with our [Row Level Security Guides](/docs/guides/auth/row-level-security).
### Row Level Security
Authentication only gets you so far. When you need granular authorization rules, nothing beats PostgreSQL's [Row Level Security (RLS)](https://www.postgresql.org/docs/current/ddl-rowsecurity.html). Supabase makes it simple to turn RLS on and off.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/rls-zoom2.mp4" type="video/mp4" muted playsInline />
</video>
### Policies
[Policies](https://www.postgresql.org/docs/current/sql-createpolicy.html) are PostgreSQL's rule engine. They are incredibly powerful and flexible, allowing you to write complex SQL rules which fit your unique business needs.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/policies-zoom2.mp4" type="video/mp4" muted playsInline />
</video>
With policies, your database becomes the rules engine. Instead of repetitively filtering your queries, like this ...
```js
const loggedInUserId = 'd0714948'
let { data, error } = await supabase
.from('users')
.select('user_id, name')
.eq('user_id', loggedInUserId)
// console.log(data)
// => { id: 'd0714948', name: 'Jane' }
```
... you can simply define a rule on your database table, `auth.uid() = user_id`, and your request will return the rows which pass the rule, even when you remove the filter from your middleware:
```js
let { data, error } = await supabase.from('users').select('user_id, name')
// console.log(data)
// Still => { id: 'd0714948', name: 'Jane' }
```
### How It Works
1. A user signs up. Supabase creates a new user in the `auth.users` table.
2. Supabase returns a new JWT, which contains the user's `UUID`.
3. Every request to your database also sends the JWT.
4. Postgres inspects the JWT to determine the user making the request.
5. The user's UID can be used in policies to restrict access to rows.
Supabase provides a special function in Postgres, `auth.uid()`, which extracts the user's UID from the JWT. This is especially useful when creating policies.
## User Management
Supabase provides multiple endpoints to authenticate and manage your users:
- [Sign up](/docs/reference/javascript/auth-signup)
- [Sign in with password](/docs/reference/javascript/auth-signinwithpassword)
- [Sign in with passwordless / one-time password (OTP)](/docs/reference/javascript/auth-signinwithotp)
- [Sign in with OAuth](/docs/reference/javascript/auth-signinwithoauth)
- [Sign out](/docs/reference/javascript/auth-signout)
When users sign up, Supabase assigns them a unique ID. You can reference this ID anywhere in your database. For example, you might create a `profiles` table referencing `id` in the `auth.users` table using a `user_id` field.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/auth-zoom2.mp4" type="video/mp4" muted playsInline />
</video>
export const Page = ({ children }) => {
const router = useRouter()
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true' && !router.asPath.includes('overview')) {
return NewContent()
}
return <Layout meta={meta} children={children} />
}
export default Page
-207
View File
@@ -1,207 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auth-apple',
title: 'Login with Apple',
description: 'Add Apple OAuth to your Supabase project',
}
To enable Apple Auth for your project, you need to set up an Apple OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Apple OAuth consists of six broad steps:
- Obtaining an `App Id` with “Sign In with Apple” capabilities.
- Obtaining a `Services Id` - this will serve as the `client_id`.
- Obtaining a `secret key` that will be used to get our `client_secret`.
- Generating the `client_secret` using the `secret key`.
- Add your `client id` and `client secret` keys to your [Supabase Project](https://app.supabase.com).
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
## Access your Apple Developer account
- Go to [developer.apple.com](https://developer.apple.com).
- Click on `Account` at the top right to log in.
![Apple Developer Portal.](/docs/img/guides/auth-apple/apple-developer-portal.png)
## Obtain an App ID
- Go to `Certificates, Identifiers & Profiles`.
- Click on `Identifiers` at the left.
- Click on the `+` sign in the upper left next to `Identifiers`.
- Select `App IDs` and click `Continue`.
- Select type `App` and click `Continue`.
- Fill out your app information:
- App description.
- Bundle ID (Apple recommends reverse-domain name style, so if your domain is acme.com and your app is called roadrunner, use: "com.acme.roadrunner").
- Scroll down and check `Sign In With Apple`.
- Click `Continue` at the top right.
- Click `Register` at the top right.
## Obtain a Services ID
This will serve as the `client_id` when you make API calls to authenticate the user.
- Go to `Certificates, Identifiers & Profiles`.
- Click on `Identifiers` at the left.
- Click on the `+` sign in the upper left next to `Identifiers`.
- Select `Services IDs` and click `Continue`.
- Fill out your information:
- App description.
- Bundle ID (you can't use the same Bundle ID from the previous step, but you can just add something to the beginning, such as "app." to make it app.com.acme.roadrunner").
- SAVE THIS ID -- this ID will become your `client_id` later.
- Click `Continue` at the top right.
- Click `Register` at the top right.
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Configure your Services ID
- Under `Identifiers`, click on your newly-created Services ID.
- Check the box next to `Sign In With Apple` to enable it.
- Click `Configure` to the right.
- Make sure your newly created Bundle ID is selected under `Primary App ID`
- Add your domain to the `Domains and Subdomains` box (do not add `https://`, just add the domain).
- In the `Return URLs` box, type the callback URL of your app which you found in the previous step and click `Next` at the bottom right.
- Click `Done` at the bottom.
- Click `Continue` at the top right.
- Click `Save` at the top right.
## Download your secret key
Now you'll need to download a `secret key` file from Apple that will be used to generate your `client_secret`.
- Go to `Certificates, Identifiers & Profiles`.
- Click on `Keys` at the left.
- Click on the `+` sign in the upper left next to `Keys`.
- Enter a `Key Name`.
- Check `Sign In with Apple`.
- Click `Configure` to the right.
- Select your newly-created Services ID from the dropdown selector.
- Click `Save` at the top right.
- Click `Continue` at the top right.
- Click `Register` at the top right.
- Click `Download` at the top right.
- Save the downloaded file -- this contains your "secret key" that will be used to generate your `client_secret`.
- Click `Done` at the top right.
## Generate a `client_secret`
The `secret key` you downloaded is used to create the `client_secret` string you'll need to authenticate your users.
According to the [Apple Docs](https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens) it needs to be a JWT
token encrypted using the Elliptic Curve Digital Signature Algorithm (ECDSA) with the P-256 curve and the SHA-256 hash algorithm.
At this time, the easiest way to generate this JWT token is with [Ruby](https://www.ruby-lang.org/en/).
If you don't have Ruby installed, you can [Download Ruby Here](https://www.ruby-lang.org/en/downloads).
- Install Ruby (or check to make sure it's installed on your system).
- Install [ruby-jwt](https://github.com/jwt/ruby-jwt).
- From the command line, run: `sudo gem install jwt`.
Create the script below using a text editor: `secret_gen.rb`
```ruby
require "jwt"
key_file = "Path to the private key"
team_id = "Your Team ID"
client_id = "The Service ID of the service you created"
key_id = "The Key ID of the private key"
validity_period = 180 # In days. Max 180 (6 months) according to Apple docs.
private_key = OpenSSL::PKey::EC.new IO.read key_file
token = JWT.encode(
{
iss: team_id,
iat: Time.now.to_i,
exp: Time.now.to_i + 86400 * validity_period,
aud: "https://appleid.apple.com",
sub: client_id
},
private_key,
"ES256",
header_fields=
{
kid: key_id
}
)
puts token
```
1. Edit the `secret_gen.rb` file:
- `key_file` = "Path to the private key you downloaded from Apple". It should look like this: `AuthKey_XXXXXXXXXX.p8`.
- `team_id` = "Your Team ID". This is found at the top right of the Apple Developer site (next to your name).
- `client_id` = "The Service ID of the service you created". This is the `Services ID` you created in the above step `Obtain a Services ID`. If you've lost this ID, you can find it in the Apple Developer Site:
- Go to `Certificates, Identifiers & Profiles`.
- Click `Identifiers` at the left.
- At the top right drop-down, select `Services IDs`.
- Find your Identifier in the list (i.e. app.com.acme.roadrunner).
- `key_id` = "The Key ID of the private key". This can be found in the name of your downloaded secret file (For a file named `AuthKey_XXXXXXXXXX.p8` your key_id is `XXXXXXXXXX`). If you've lost this ID, you can find it in the Apple Developer Site:
- Go to `Certificates, Identifiers & Profiles`.
- Click `Keys` at the left.
- Click on your newly-created key in the list.
- Look under `Key ID` to find your key_id.
2. From the command line, run: `ruby secret_gen.rb > client_secret.txt`.
3. Your `client_secret` is now stored in this `client_secret.txt` file.
## Add your OAuth credentials to Supabase
- Go to your [Supabase Dashboard](https://app.supabase.com).
- In the left sidebar, click the `Authentication` icon (near the top).
- Click `Settings` from the list to go to the `Authentication Settings` page.
- Enter the final (hosted) URL of your app under `Site URL` (this is important).
- Under `External OAuth Providers` turn `Apple Enabled` to ON.
- Enter your `client_id` and `client_secret` saved in the previous steps.
- Click `Save`.
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `apple` as the `provider`:
```js
async function signInWithApple() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'apple',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Apple Developer Account](https://developer.apple.com).
- [Ruby](https://www.ruby-lang.org/en/) Docs.
- [ruby-jwt](https://github.com/jwt/ruby-jwt) library.
- Thanks to [Janak Amarasena](https://medium.com/@janakda) who did all the heavy lifting in [How to configure Sign In with Apple](https://medium.com/identity-beyond-borders/how-to-configure-sign-in-with-apple-77c61e336003).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
-103
View File
@@ -1,103 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auth-azure',
title: 'Login with Azure',
description: 'Add Azure OAuth to your Supabase project',
}
To enable Azure Auth for your project, you need to set up an Azure OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Azure OAuth consists of four broad steps:
- Create an application under Azure Active Directory.
- Obtain a `Application (client) ID` with “Sign In with Azure” capabilities. This will be used as the `client id`.
- Create a `Secret ID` with “Sign In with Azure” capabilities. The value of the secret will be used as the `client secret`.
- Add the callback url of your application to the allowlist.
## Access your Azure Developer account
- Go to [portal.azure.com](https://portal.azure.com/#home).
- Login and select "Azure Active Directory" under the list of Azure Services.
## Register an application
- Under Azure Active Directory, select "App registrations" in the side panel.
- Select "New registration".
- Choose a name and select your preferred option for the supported account types.
- Specify the "Redirect URI".
- 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.](/docs/img/guides/auth-azure/azure-register-app.png)
## Obtain a Client ID
This will serve as the `client_id` when you make API calls to authenticate the user.
- 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](/docs/img/guides/auth-azure/azure-client-id.png)
## Obtain a Secret ID
This will serve as the `client_secret` when you make API calls to authenticate the user.
- Click on the name of the app registered above.
- Under "Essentials", click on "Client credentials".
- Navigate to the "Client secrets" tab and select "New client secret".
- 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](/docs/img/guides/auth-azure/azure-client-secret.png)
## Obtain the Tenant URL
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](/docs/img/guides/auth-azure/azure-tenant-url.png)
## Add login code to your client app
<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`:
```js
async function signInWithAzure() {
const { data, error } = await supabase.auth.signInWithOAuth(
{
provider: 'azure',
},
{
scopes: 'email',
}
)
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Azure Developer Account](https://portal.azure.com)
- [GitHub Discussion](https://github.com/supabase/gotrue/pull/54#issuecomment-757043573)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,95 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auth-bitbucket',
title: 'Login with Bitbucket',
description: 'Add Bitbucket OAuth to your Supabase project',
}
To enable Bitbucket Auth for your project, you need to set up a BitBucket OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up Bitbucket logins for your application consists of 3 parts:
- Create and configure a Bitbucket OAuth Consumer on [Bitbucket](https://bitbucket.org)
- Add your Bitbucket OAuth Consumer keys to your [Supabase Project](https://app.supabase.com)
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
## Access your Bitbucket account
- Go to [bitbucket.org](https://bitbucket.org/).
- Click on `Login` at the top right to log in.
![Bitbucket Developer Portal.](/docs/img/guides/auth-bitbucket/bitbucket-portal.png)
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create a Bitbucket OAuth app
- Click on your profile icon at the bottom left
- Click on `All Workspaces`
- Select a workspace and click on it to select it
- Click on `Settings` on the left
- Click on `OAuth consumers` on the left under `Apps and Features` (near the bottom)
- Click `Add Consumer` at the top
- Enter the name of your app under `Name`
- In `Callback URL`, type the callback URL of your app
- Check the permissions you need (Email, Read should be enough)
- Click `Save` at the bottom
- Click on your app name (the name of your new OAuth Consumer)
- Copy your `Key` (`client_key`) and `Secret` (`client_secret`) codes
## Add your Bitbucket credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
- In the left sidebar, click the `Authentication` icon (near the top)
- Click `Settings` from the list to go to the `Authentication Settings` page
- Enter the final (hosted) URL of your app under `Site URL` (this is important)
- Under `External OAuth Providers` turn `Bitbucket Enabled` to ON
- Enter your `client_id` and `client_secret` saved in the previous step
- Click `Save`
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `bitbucket` as the `provider`:
```js
async function signInWithBitbucket() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'bitbucket',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Bitbucket Account](https://bitbucket.org)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,97 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auth-discord',
title: 'Login with Discord',
description: 'Add Discord OAuth to your Supabase project',
}
To enable Discord Auth for your project, you need to set up a Discord Application and add the Application OAuth credentials to your Supabase Dashboard.
## Overview
Setting up Discord logins for your application consists of 3 parts:
- Create and configure a Discord Application [Discord Developer Portal](https://discord.com/developers)
- Add your Discord OAuth Consumer keys to your [Supabase Project](https://app.supabase.com)
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
## Access your Discord account
- Go to [discord.com](https://discord.com/).
- Click on `Login` at the top right to log in.
![Discord Portal.](/docs/img/guides/auth-discord/discord-portal.png)
- Once logged in, go to [discord.com/developers](https://discord.com/developers).
![Discord Portal.](/docs/img/guides/auth-discord/discord-developer-portal.png)
## Find your callback URL
In the next step you require a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create a Discord Application
- Click on `New Application` at the top right.
- Enter the name of your application and click `Create`.
- Click on `OAuth2` under `Settings` in the left side panel.
- Click `Add Redirect` under `Redirects`.
- Type or paste your `callback URL` into the `Redirects` box.
- Click `Save Changes` at the bottom.
- Copy your `Client ID` and `Client Secret` under `Client information`.
## Add your Discord credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
- In the left sidebar, click the `Authentication` icon (near the top)
- Click `Settings` from the list to go to the `Authentication Settings` page
- Enter the final (hosted) URL of your app under `Site URL` (this is important)
- Under `External OAuth Providers` turn `Discord Enabled` to ON
- Enter your `client_id` and `client_secret` saved in the previous step
- Click `Save`
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `discord` as the `provider`:
```js
async function signInWithDiscord() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'discord',
})
}
```
If your user is already signed in, Discord prompts the user again for authorization.
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Discord Account](https://discord.com)
- [Discord Developer Portal](https://discord.com/developers)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,112 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auth-facebook',
title: 'Login with Facebook',
description: 'Add Facebook OAuth to your Supabase project',
}
To enable Facebook Auth for your project, you need to set up a Facebook OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up Facebook logins for your application consists of 3 parts:
- Create and configure a Facebook Application on the [Facebook Developers Site](https://developers.facebook.com)
- Add your Facebook keys to your [Supabase Project](https://app.supabase.com)
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
## Access your Facebook Developer account
- Go to [developers.facebook.com](https://developers.facebook.com).
- Click on `Log In` at the top right to log in.
![Facebook Developer Portal.](/docs/img/guides/auth-facebook/facebook-portal.png)
## Create a Facebook App
- Click on `My Apps` at the top right.
- Click `Create App` near the top right.
- Select your app type and click `Continue`.
- Fill in your app information, then click `Create App`.
- This should bring you to the screen: `Add Products to Your App`. (Alternatively you can click on `Add Product` in the left sidebar to get to this screen.)
## Find your callback URI
The next step requires a callback URI, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Set up FaceBook Login for your Facebook App
From the `Add Products to your App` screen:
- Click `Setup` under `Facebook Login`
- Skip the Quickstart screen, instead, in the left sidebar, click `Settings` under `Facebook Login`
- Enter your callback URI under `Valid OAuth Redirect URIs` on the `Facebook Login Settings` page
- Enter this in the `Valid OAuth Redirect URIs` box
- Click `Save Changes` at the bottom right
Be aware that you have to set the right access levels on your Facebook App to enable 3rd party applications to read the email address.
From the `App Review -> Permissions and Features` screen:
- Click the button `Request Advanced Access` on the right side of `public_profile` and `email`
You can read more about access levels [here](https://developers.facebook.com/docs/graph-api/overview/access-levels/)
## Copy your Facebook App ID and Secret
- Click `Settings / Basic` in the left sidebar
- Copy your App ID from the top of the `Basic Settings` page
- Under `App Secret` click `Show` then copy your secret
- Make sure all required fields are completed on this screen.
## Enter your Facebook App ID and Secret into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
- In the left sidebar, click the `Authentication` icon (near the top)
- Click `Settings` from the list to go to the `Authentication Settings` page
- Enter the final (hosted) URL of your app under `Site URL` (this is important)
- Under `External OAuth Providers` turn `Facebook Enabled` to ON
- Enter your `Facebook client ID` and `Facebook secret` saved in the previous step
- Click `Save`
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `facebook` as the `provider`:
```js
async function signInWithFacebook() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'facebook',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Facebook Developers Dashboard](https://developers.facebook.com/)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
-105
View File
@@ -1,105 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auth-github',
title: 'Login with GitHub',
description: 'Add GitHub OAuth to your Supabase project',
}
To enable GitHub Auth for your project, you need to set up a GitHub OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up GitHub logins for your application consists of 3 parts:
- Create and configure a GitHub OAuth App on [GitHub](https://github.com)
- Add your GitHub OAuth keys to your [Supabase Project](https://app.supabase.com)
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
## Access your GitHub account
- Go to [github.com](https://github.com).
- Click on `Sign In` at the top right to log in.
![GitHub Developer Portal.](/docs/img/guides/auth-github/github-portal.png)
## Create a GitHub Oauth App
Go to the [GitHub Developer Settings](https://github.com/settings/developers) page:
- Click on your profile photo at the top right
- Click Settings near the bottom of the menu
- In the left sidebar, click `Developer settings` (near the bottom)
- In the left sidebar, click `OAuth Apps`
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Register a new OAuth application
- Click `Register a new application`. If you've created an app before, click `New OAuth App` here.
- In `Application name`, type the name of your app.
- In `Homepage URL`, type the full URL to your app's website.
- In `Authorization callback URL`, type the callback URL of your app.
- Enter the URL in the `Valid OAuth Redirect URIs` box.
- Click `Save Changes` at the bottom right.
- Click `Register Application`.
Copy your new OAuth credentials
- Copy and save your `Client ID`.
- Click `Generate a new client secret`.
- Copy and save your `Client secret`.
## Enter your GitHub credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
- In the left sidebar, click the `Authentication` icon (near the top)
- Click `Settings` from the list to go to the `Authentication Settings` page
- Enter the final (hosted) URL of your app under `Site URL` (this is important)
- Under `External OAuth Providers` turn `GitHub Enabled` to ON
- Enter your `GitHub Client ID` and `GitHub Client Secret` saved in the previous step
- Click `Save`
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `github` as the `provider`:
```js
async function signInWithGitHub() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [GitHub Developer Settings](https://github.com/settings/developers)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,92 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auth-gitlab',
title: 'Login with GitLab',
description: 'Add GitLab OAuth to your Supabase project',
}
To enable GitLab Auth for your project, you need to set up a GitLab OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up GitLab logins for your application consists of 3 parts:
- Create and configure a GitLab Application on [GitLab](https://gitlab.com)
- Add your GitLab Application keys to your [Supabase Project](https://app.supabase.com)
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
## Access your GitLab account
- Go to [gitlab.com](https://gitlab.com).
- Click on `Login` at the top right to log in.
![GitLab Developer Portal.](/docs/img/guides/auth-gitlab/gitlab-portal.png)
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create your GitLab Application
- Click on your `profile logo` (avatar) in the top-right corner.
- Select `Edit profile`.
- In the left sidebar, select Applications.
- Enter the name of the application.
- In the `Redirect URI` box, type the callback URL of your app.
- Check the box next to `Confidential` (make sure it is checked).
- Check the scope named `read_user` (this is the only required scope).
- Click `Save Application` at the bottom.
- Copy and save your `Application ID` (`client_id`) and `Secret` (`client_secret`) which you'll need later.
## Add your GitLab credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- In the left sidebar, click the `Authentication` icon (near the top).
- Click `Settings` from the list to go to the `Authentication Settings` page.
- Enter the final (hosted) URL of your app under `Site URL` (this is important).
- Under `External OAuth Providers` turn `GitLab Enabled` to ON.
- Enter your `client_id` and `client_secret` saved in the previous step.
- Click `Save`.
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `gitlab` as the `provider`:
```js
async function signInWithGitLab() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'gitlab',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [GitLab Account](https://gitlab.com)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
-120
View File
@@ -1,120 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auth-google',
title: 'Login with Google',
description: 'Add Google OAuth to your Supabase project',
}
To enable Google Auth for your project, you need to set up a Google OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up Google logins for your application consists of 3 parts:
- Create and configure a Google Project on the [Google Cloud Platform Console](https://console.cloud.google.com/home/dashboard)
- Add your Google OAuth keys to your [Supabase Project](https://app.supabase.com)
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
## Access your Google Cloud Platform account
- Go to [cloud.google.com](https://cloud.google.com).
- Click on `Sign in` at the top right to log in.
![Google Developer Portal.](/docs/img/guides/auth-google/google-portal.png)
## Create a Google Cloud Platform Project
- Click on `Select a Project` at the top left.
- (Or, if a project is currently selected, click on the current project name at the top left.)
- Click `New Project` at the top right.
- Fill in your app information, then click `Create`.
- (This can take a few minutes.)
- This should bring you to the dashboard for your new project.
## Create the OAuth Keys for your project
From your project's dashboard screen:
- In the search bar at the top labeled `Search products and resources` type `OAuth`.
- Click on `OAuth consent screen` from the list of results.
- On the `OAuth consent screen` page select `External`.
- Click `Create`.
## Edit your app information
- On the `Edit app registration` page fill out your app information.
- Click `Save and continue` at the bottom.
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create your Google credentials
- Click `Credentials` at the left to go to the `Credentials` page on the Google Cloud Platform console.
- Click `Create Credentials` near the top then select `OAuth client ID`
- On the `Create OAuth client ID` page, select your application type. If you're not sure, choose `Web application`.
- Fill in your app name.
- At the bottom, under `Authorized redirect URIs` click `Add URI`.
- Enter your callback URI under `Authorized redirect URIs` at the bottom.
- Enter your callback URI in the `Valid OAuth Redirect URIs` box.
- Click `Save Changes` at the bottom right.
- Click `Create`.
Copy your new OAuth credentials
- A box will appear called `OAuth client created`.
- Copy and save the values under `Your Client ID` and `Your Client Secret`.
## Enter your Google credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
- In the left sidebar, click the `Authentication` icon (near the top)
- Click `Settings` from the list to go to the `Authentication Settings` page
- Enter the final (hosted) URL of your app under `Site URL` (this is important)
- Under `External OAuth Providers` turn `Google Enabled` to ON
- Enter your `Google Client ID` and `Google Client Secret` saved in the previous step
- Click `Save`
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `google` as the `provider`:
```js
async function signInWithGoogle() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Google Cloud Platform Console](https://console.cloud.google.com/home/dashboard)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -34,7 +34,10 @@ 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} />
```
@@ -43,13 +46,18 @@ This renders the Auth component without any styling.
We recommend using one of the predefined themes to style the UI.
Import the theme you want to use and pass it to the `appearence.theme` prop.
```js lines=4,11 title=/src/index.js
```js lines=4,16 title=/src/index.js
import {
Auth,
// Import predefined theme
ThemeSupa,
} from '@supabase/auth-ui-react'
const supabase = createClient(
'<INSERT PROJECT URL>',
'<INSERT PROJECT ANON API KEY>'
)
const App = () => (
<Auth
supabaseClient={supabase}
@@ -59,6 +67,28 @@ const App = () => (
)
```
### Social Providers
The Auth component also supports login with [offical social providers](../../auth#providers).
```js lines=13 title=/src/index.js
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 App = () => (
<Auth
supabaseClient={supabase}
appearance={{ theme: ThemeSupa }}
providers={['google', 'facebook', 'twitter']}
/>
)
```
## Customization
There are several ways to customize Auth UI:
@@ -74,11 +104,14 @@ There are several ways to customize Auth UI:
Auth UI comes with several themes to customize the appearance. Each predefined theme comes with at least two variations, a `default` variation, and a `dark` variation. You can switch between these themes using the `theme` prop. Import the theme you want to use and pass it to the `appearence.theme` prop.
```js lines=2,10 title=/src/index.js
```js lines=2,13 title=/src/index.js
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
@@ -99,11 +132,14 @@ Currently there is only one predefined theme available, but we plan to add more.
Auth UI comes with two theme variations: `default` and `dark`. You can switch between these themes with the `theme` prop.
```js lines=11 title=/src/index.js
```js lines=14 title=/src/index.js
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
@@ -121,11 +157,14 @@ If you don't pass a value to `theme` it uses the `"default"` theme. You can pass
Auth UI themes can be overridden using variable tokens. See the [list of variable tokens](https://github.com/supabase-community/auth-ui/blob/main/packages/react/common/theming/Themes.tsx).
```js lines=11-18 title=/src/index.js
```js lines=14-21 title=/src/index.js
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
@@ -208,7 +247,10 @@ 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
@@ -233,7 +275,10 @@ 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
@@ -257,7 +302,10 @@ 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
@@ -1,92 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auth-keycloak',
title: 'Login with Keycloak',
description: 'Add Keycloak OAuth to your Supabase project',
}
To enable Keycloak Auth for your project, you need to set up an Keycloak OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
To get started with Keycloak, you can run it in a docker container with: `docker run -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -p 8080:8080 jboss/keycloak:latest`
This guide will be assuming that you are running keycloak in a docker container as described in the command above.
Keycloak OAuth consists of five broad steps:
- Create a new client in your specified keycloak realm.
- Obtain the `issuer` from the "OpenID Endpoint Configuration". This will be used as the `Keycloak URL`.
- Ensure that the new client has the "Client Protocol" set to "openid-connect" and the "Access Type" is set to "confidential".
- The `Client ID` of the client created will be used as the `client id`.
- Obtain the `Secret` from the credentials tab which will be used as the `client secret`.
- Add the callback url of your application to your allowlist.
## Access your Keycloak Admin console
- Login by visiting [`http://localhost:8080`](http://localhost:8080) and clicking on "Administration Console".
## Create a Keycloak Realm
- Once you've logged in to the Keycloak console, you can add a realm from the side panel. The default realm should be named "Master".
- 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.](/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](/docs/img/guides/auth-keycloak/keycloak-add-client.png)
## Client Settings
After you've created the client successfully, ensure that you set the following settings:
1. The "Client Protocol" should be set to "openid-connect".
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](/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](/docs/img/guides/auth-keycloak/keycloak-client-secret.png)
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `keycloak` as the `provider`:
```js
async function signInWithKeycloak() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'keycloak',
options: {
scopes: 'openid',
},
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- You can find the keycloak openid endpoint configuration under the realm settings.
![Keycloak OpenID Endpoint Configuration](/docs/img/guides/auth-keycloak/keycloak-openid-endpoint-config.png)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,91 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auth-linkedin',
title: 'Login with LinkedIn',
description: 'Add LinkedIn OAuth to your Supabase project',
}
To enable LinkedIn Auth for your project, you need to set up a LinkedIn OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up LinkedIn logins for your application consists of 3 parts:
- Create and configure a LinkedIn Project and App on the [LinkedIn Developer Dashboard](https://www.linkedin.com/developers/apps).
- Add your LinkedIn `client_id` and `client_secret` to your [Supabase Project](https://app.supabase.com).
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
## Access your LinkedIn Developer account
- Go to [LinkedIn Developer Dashboard](https://www.linkedin.com/developers/apps).
- Log in (if necessary.)
![LinkedIn Developer Portal](/docs/img/guides/auth-linkedin/linkedin_developers_page.png)
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create a LinkedIn OAuth app
- Go to [LinkedIn Developer Dashboard](https://www.linkedin.com/developers/apps).
- Click on `Create App` at the top right
- Enter your `LinkedIn Page` and `App Logo`
- Save your app
- Click `Auth` from the top menu
- Add your `Redirect URL` to the `Authorized Redirect URLs for your app` section
- Copy and save your newly-generated `Client ID`
- Copy and save your newly-generated `Client Secret`
## Enter your LinkedIn credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- In the left sidebar, click the `Authentication` icon (near the top).
- Click `Settings` from the list to go to the `Authentication Settings` page.
- Enter the final (hosted) URL of your app under `Site URL` (this is important).
- Under `External OAuth Providers` turn `LinkedIn Enabled` to ON.
- Enter your `client_id` and `client_secret` saved in the previous step.
- Click `Save`.
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `linkedin` as the `provider`:
```js
async function signInWithLinkedIn() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'linkedin',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [LinkedIn Developer Dashboard](https://api.LinkedIn.com/apps)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -30,13 +30,15 @@ Setting up Magic Link logins for your Supabase application.
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
When your user signs in, call [signInWithOtp()](/docs/reference/javascript/auth-signinwithotp) with their email address:
```js
async function signInWithEmail() {
const { data, error } = await supabase.auth.signInWithOtp({
email: 'example@email.com',
options: {
emailRedirectTo: 'https://example.com/welcome',
},
})
}
```
@@ -62,7 +64,6 @@ Future<void> signInWithEmail() async {
defaultActiveId="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:
```js
@@ -1,95 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auth-notion',
title: 'Login with Notion',
description: 'Add Notion OAuth to your Supabase project',
}
To enable Notion Auth for your project, you need to set up a Notion Application and add the Application OAuth credentials to your Supabase Dashboard.
## Overview
Setting up Notion logins for your application consists of 3 parts:
- Create and configure a Notion Application [Notion Developer Portal](https://www.notion.so/my-integrations)
- Retrieve your OAuth client ID and OAuth client secret and add them to your [Supabase Project](https://app.supabase.com)
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
## Create your notion integration
- Go to [developers.notion.com](https://developers.notion.com/).
- Click "View my integrations" and login.
![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](/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](/docs/img/guides/auth-notion/notion-redirect-uri.png)
You can retrieve the redirect uri with the following steps:
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- Add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
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 />
</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](/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)
- Click `Settings` from the list to go to the `Authentication Settings` page
- Under `External OAuth Providers` turn `Notion Enabled` to ON
- Enter the "OAuth client ID" and "OAuth client secret" obtained in the `client id` and `client secret` fields.
- Click `Save`
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `notion` as the `provider`:
```js
async function signInWithNotion() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'notion',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Notion Account](https://notion.so)
- [Notion Developer Portal](https://www.notion.so/my-integrations)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
-104
View File
@@ -1,104 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auth-slack',
title: 'Login with Slack',
description: 'Add Slack OAuth to your Supabase project',
}
To enable Slack Auth for your project, you need to set up a Slack OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up Slack logins for your application consists of 3 parts:
- Create and configure a Slack Project and App on the [Slack Developer Dashboard](https://api.slack.com/apps).
- Add your Slack `API Key` and `API Secret Key` to your [Supabase Project](https://app.supabase.com).
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
## Access your Slack Developer account
- Go to [api.slack.com](https://api.slack.com/apps).
- Click on `Your Apps` at the top right to log in.
![Slack Developer Portal.](/docs/img/guides/auth-slack/slack-portal.png)
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create a Slack OAuth app
- Go to [api.slack.com](https://api.slack.com/apps).
- Click on `Create an App`
Under `Create an app...`:
- Click `From scratch`
- Type the name of your app
- Select your `Slack Workspace`
- Click `Create App`
Under `App Credentials`:
- Copy and save your newly-generated `Client ID`
- Copy and save your newly-generated `Client Secret`
- Click `Permissions`
Under `Redirect URLs`:
- Click `Add New Redirect URL`
- Paste your `Callback URL` then click `Add`
- Click `Save URLs`
## Enter your Slack credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- In the left sidebar, click the `Authentication` icon (near the top).
- Click `Settings` from the list to go to the `Authentication Settings` page.
- Enter the final (hosted) URL of your app under `Site URL` (this is important).
- Under `External OAuth Providers` turn `Slack Enabled` to ON.
- Enter your `Client ID` (`client_id`) and `Client Secret` (`client_secret`) saved in the previous step.
- Click `Save`.
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `slack` as the `provider`:
```js
async function signInWithSlack() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'slack',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Slack Developer Dashboard](https://api.slack.com/apps)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,99 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auth-spotify',
title: 'Login with Spotify',
description: 'Add Spotify OAuth to your Supabase project',
}
To enable Spotify Auth for your project, you need to set up a Spotify OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up Spotify logins for your application consists of 3 parts:
- Create and configure a Spotify Project and App on the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard/).
- Add your Spotify `API Key` and `API Secret Key` to your [Supabase Project](https://app.supabase.com).
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
## Access your Spotify Developer account
- Log into [Spotify](https://spotify.com)
- Access the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard)
![Spotify Developer Portal.](/docs/img/guides/auth-spotify/spotify-portal.png)
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create a Spotify OAuth app
- Log into [Spotify](https://spotify.com).
- Go to the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard)
- Click `Create an App`
- Type your `App name`
- Type your `App description`
- Check the box to agree with the `Developer TOS and Branding Guidelines`
- Click `Create`
- Save your `Client ID`
- Save your `Client Secret`
- Click `Edit Settings`
Under `Redirect URIs`:
- Paste your Supabase Callback URL in the box
- Click `Add`
- Click `Save` at the bottom
## Enter your Spotify credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- In the left sidebar, click the `Authentication` icon (near the top).
- Click `Settings` from the list to go to the `Authentication Settings` page.
- Enter the final (hosted) URL of your app under `Site URL` (this is important).
- Under `External OAuth Providers` turn `Spotify Enabled` to ON.
- Enter your `Client ID` (`client_id`) and `Client Secret` (`client_secret`) saved in the previous step.
- Click `Save`.
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `spotify` as the `provider`:
```js
async function signInWithSpotify() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'spotify',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Spotify Developer Dashboard](https://developer.spotify.com/dashboard/)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
-111
View File
@@ -1,111 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auth-twitch',
title: 'Login with Twitch',
description: 'Add Twitch OAuth to your Supabase project',
}
To enable Twitch Auth for your project, you need to set up a Twitch Application and add the Application OAuth credentials to your Supabase Dashboard.
## Overview
Setting up Twitch logins for your application consists of 3 parts:
- Create and configure a Twitch Application [Twitch Developer Console](https://dev.twitch.tv/console)
- Add your Twitch OAuth Consumer keys to your [Supabase Project](https://app.supabase.com)
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
## Access your Twitch Developer account
- Go to [dev.twitch.tv](https://dev.twitch.tv).
- 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](/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](/docs/img/guides/auth-twitch/twitch-console.png)
## Find your callback URL
In the next step you require a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create a Twitch Application
![Twitch Developer Console](/docs/img/guides/auth-twitch/twitch-console.png)
- Click on `+ Register Your Application` at the top right.
![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.)
- Select a category for your app.
- Check the Captcha box and click `Create`.
## Retrieve your Twitch OAuth Client ID and Client Secret
- Click `Manage` at the right of your application entry in the list.
![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](/docs/img/guides/auth-twitch/twitch-get-keys.png)
## Add your Twitch credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
- In the left sidebar, click the `Authentication` icon (near the top)
- Click `Settings` from the list to go to the `Authentication Settings` page
- Enter the final (hosted) URL of your app under `Site URL` (this is important)
- Under `External OAuth Providers` turn `Twitch Enabled` to ON
- Enter your `client_id` and `client_secret` saved in the previous step
- Click `Save`
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `twitch` as the `provider`:
```js
async function signInWithTwitch() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'twitch',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Twitch Account](https://twitch.tv)
- [Twitch Developer Console](https://dev.twitch.tv/console)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,100 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auth-twitter',
title: 'Login with Twitter',
description: 'Add Twitter OAuth to your Supabase project',
}
To enable Twitter Auth for your project, you need to set up a Twitter OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up Twitter logins for your application consists of 3 parts:
- Create and configure a Twitter Project and App on the [Twitter Developer Dashboard](https://developer.twitter.com/en/portal/dashboard).
- Add your Twitter `API Key` and `API Secret Key` to your [Supabase Project](https://app.supabase.com).
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
## Access your Twitter Developer account
- Go to [developer.twitter.com](https://developer.twitter.com).
- Click on `Sign in` at the top right to log in.
![Twitter Developer Portal.](/docs/img/guides/auth-twitter/twitter-portal.png)
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create a Twitter OAuth app
- Click `+ Create Project`.
- Enter your project name, click `Next`.
- Select your use case, click `Next`.
- Enter a description for your project, click `Next`.
- Enter a name for your app, click `Complete`.
- Copy and save your `API Key` (this is your `client_id`).
- Copy and save your `API Secret Key` (this is your `client_secret`).
- At the bottom, under `Next, setup your App` click the link `enable 3rd party authentication`.
- Under `App Settings`, click on the gear icon next to your app name to go to `App Settings`.
- At the bottom, next to `Authentication settings`, click `Edit`.
- Turn `Enable 3-legged OAuth` ON.
- Turn `Request email address from users` ON.
- Enter your `Callback URL`.
- Enter your `Website URL`.
- Enter your `Terms of service URL`.
- Enter your `Privacy policy URL`.
- Click `Save`.
## Enter your Twitter credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- In the left sidebar, click the `Authentication` icon (near the top).
- Click `Settings` from the list to go to the `Authentication Settings` page.
- Enter the final (hosted) URL of your app under `Site URL` (this is important).
- Under `External OAuth Providers` turn `Twitter Enabled` to ON.
- Enter your `API Key` (`client_id`) and `API Secret Key` (`client_secret`) saved in the previous step.
- Click `Save`.
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `twitter` as the `provider`:
```js
async function signInWithTwitter() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'twitter',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Twitter Developer Dashboard](https://developer.twitter.com/en/portal/dashboard)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,93 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auth-workos',
title: 'Login with WorkOS',
description: 'Add WorkOS OAuth to your Supabase project',
}
To enable WorkOS Auth for your project, you need to set up WorkOS OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
In this guide, we will cover how to use Supabase OAuth with WorkOS to implement Single-Sign-On(SSO).
The procedure consists of five broad steps:
- Create a new organization from your WorkOS Dashboard.
- Obtain the `Client ID` from the Configuration tab and configure redirect URI.
- Obtain the `WorkOS Secret` from the credentials tab.
- Connect a WorkOS Supported Identity Provider
- Add your WorkOS credentials into your Supabase project
## Create a WorkOS Organization
Log in to the dashboard and hop over to the Organizations tab to create and organization
![Create an Organization](/docs/img/guides/auth-workos/workos-create-organization.png)
## Obtain the Client ID and configure Redirect URI
Head over to the Configuration tab and configure the redirect URI.The redirect URI should look like `https://<project-ref>.supabase.co/auth/v1/callback`
Note that this is distinct from the redirect URI referred to in the Supabase dashboard
![Fetch Client ID and configure Redirect URI](/docs/img/guides/auth-workos/workos-clientid-redirect-uri.png)
## Obtain the WorkOS Secret
Head over to the API Keys page and obtain the secret key.
![WorkOS Secret Key](/docs/img/guides/auth-workos/workos-secret-key.png)
## Connect a WorkOS Supported Identity Provider
Set up the identity provider by visiting the setup link.
![Visiting the setup link](/docs/img/guides/auth-workos/workos-setup-identity-provider.png)
You can pick between any one of the many identity providers that WorkOS supports.
## Add your WorkOS credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
- In the left sidebar, click the `Authentication` icon (near the top)
- Click `Settings` from the list to go to the `Authentication Settings` page
- Under `External OAuth Providers` turn `WorkOS Enabled` to ON
- Enter the `Client ID`, `Secret`, and `WorkOS URL` saved in the previous steps. The ``WorkOS URL` setting should be set to https://api.workos.com/
- Click `Save`
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `workos` as the `provider`:
```js
async function signInWithWorkOS() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'workos',
options: {
queryParams: {
connection: '<your_connection>',
organization: '<your_organization',
provider: '<your_provider>',
},
},
})
}
```
Refer to the [WorkOS Documentation](https://workos.com/docs/reference/sso/authorize/) to learn more about the different methods.
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [WorkOS Documentation](https://workos.com/docs/sso/guide)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
-100
View File
@@ -1,100 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auth-zoom',
title: 'Login with Zoom',
description: 'Add Zoom OAuth to your Supabase project',
}
To enable Zoom Auth for your project, you need to set up a Zoom OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up Zoom logins for your application consists of 3 parts:
- Create and configure a Zoom OAuth App on [Zoom App Marketplace](https://marketplace.zoom.us/)
- Add your Zoom OAuth keys to your [Supabase Project](https://app.supabase.com)
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
## Access your Zoom Developer account
- Go to [marketplace.zoom.us](https://marketplace.zoom.us/).
- Click on `Sign In` at the top right to log in.
![Zoom Developer Portal.](/docs/img/guides/auth-zoom/zoom-portal.png)
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create a Zoom Oauth App
- Go to [marketplace.zoom.us](https://marketplace.zoom.us/).
- Click on `Sign In` at the top right to log in.
- Click `Build App` (from the dropdown Develop)
- In the OAuth card, click `Create`
- Type the name of your app
- Choose app type
- Click `Create`
Under `App credentials`
- Copy and save your `Client ID`.
- Copy and save your `Client secret`.
Under `Redirect URL for OAuth`
- Paste your `Callback URL`
- Click `Continue`
## Enter your Zoom credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
- In the left sidebar, click the `Authentication` icon (near the top)
- Click `Settings` from the list to go to the `Authentication Settings` page
- Enter the final (hosted) URL of your app under `Site URL` (this is important)
- Under `External OAuth Providers` turn `Zoom Enabled` to ON
- Enter your `Zoom Client ID` and `Zoom Client Secret` saved in the previous step
- Click `Save`
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `zoom` as the `provider`:
```js
async function signInWithZoom() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'zoom',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Zoom App Marketplace](https://marketplace.zoom.us/)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+170 -3
View File
@@ -1,5 +1,6 @@
import Layout from '~/layouts/DefaultGuideLayout'
import OldContent from '~/pages/guides/auth.mdx'
import NewContent from '~/pages/new/auth.mdx'
import { useRouter } from 'next/router'
export const meta = {
id: 'auth',
@@ -8,9 +9,175 @@ export const meta = {
sidebar_label: 'Overview',
}
## Overview
There are two parts to every Auth system:
- **Authentication:** should this person be allowed in? If yes, who are they?
- **Authorization:** once they are in, what are they allowed to do?
Supabase Auth is designed to work either as a standalone product, or deeply integrated with the other Supabase products.
Postgres is at the heart of everything we do, and the Auth system follows this principle. We leverage Postgres' built-in Auth functionality wherever possible.
Here's a quick, 2 minute tour of the Auth features built-in to Supabase:
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/6ow_jW4epf8"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
## Authentication
You can authenticate your users in several ways:
- Email & password.
- Magic links (one-click logins).
- Social providers.
- Phone logins.
### Providers
We provide a suite of Providers and login methods, as well as [Auth helpers](/docs/guides/auth/auth-helpers/).
<div className="container" style={{ padding: 0 }}>
<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.](/docs/img/supabase-oauth-logins.png)
### Redirect URLs and wildcards
When using third-party providers, the [Supabase client library](/docs/reference/javascript/auth-signinwithoauth#sign-in-using-a-third-party-provider-with-redirect) redirects the user to the provider. When the third-party provider successfully authenticates the user, the provider redirects the user to the Supabase Auth callback URL where they are further redirected to the URL specified in the `redirectTo` parameter. This parameter defaults to the [`SITE_URL`](/docs/reference/auth/config#site_url). You can modify the `SITE_URL` or [add additional redirect URLs](https://app.supabase.com/project/_/auth/settings).
You can use wildcard match patterns to support preview URLs from providers like Netlify and Vercel. See the [full list of supported patterns](https://pkg.go.dev/github.com/gobwas/glob#Compile). Use [this tool](https://www.digitalocean.com/community/tools/glob?comments=true&glob=http%3A%2F%2Flocalhost%3A3000%2F%2A%2A&matches=false&tests=http%3A%2F%2Flocalhost%3A3000&tests=http%3A%2F%2Flocalhost%3A3000%2F&tests=http%3A%2F%2Flocalhost%3A3000%2F%3Ftest%3Dtest&tests=http%3A%2F%2Flocalhost%3A3000%2Ftest-test%3Ftest%3Dtest&tests=http%3A%2F%2Flocalhost%3A3000%2Ftest%2Ftest%3Ftest%3Dtest) to test your patterns.
> ⚠️ WARNING: While the "globstar" (`**`) is useful for local development and preview URLs, we recommend setting the exact redirect URL path for your site URL in production.
#### Netlify preview URLs
For deployments with Netlify, set the `SITE_URL` to your official site URL. Add the following additional redirect URLs for local development and deployment previews:
- `http://localhost:3000/**`
- `https://**--my_org.netlify.app/**`
#### Vercel preview URLs
For deployments with Vercel, set the `SITE_URL` to your official site URL. Add the following additional redirect URLs for local development and deployment previews:
- `http://localhost:3000/**`
- `https://*-username.vercel.app/**`
Vercel provides an environment variable for the URL of the deployment called `NEXT_PUBLIC_VERCEL_URL`. See the [Vercel docs](https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables) for more details. You can use this variable to dynamically redirect depending on the environment:
```js
const getURL = () => {
let url =
process?.env?.NEXT_PUBLIC_SITE_URL ?? // Set this to your site URL in production env.
process?.env?.NEXT_PUBLIC_VERCEL_URL ?? // Automatically set by Vercel.
'http://localhost:3000/';
// Make sure to include `https://` when not localhost.
url = url.includes('http') ? url : `https://${url}`;
// Make sure to including trailing `/`.
url = url.charAt(url.length - 1) === '/' ? url : `${url}/`;
return url;
};
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github'
options: {
redirectTo: getURL()
}
}
```
#### Mobile deep linking URIs
For mobile applications you can use deep linking URIs. For example for your `SITE_URL` you can specify something like `com.supabase://login-callback/` and for additional redirect URLs something like `com.supabase.staging://login-callback/` if needed.
## Authorization
When you need granular authorization rules, nothing beats PostgreSQL's Row Level Security (RLS).
Policies are PostgreSQL's rule engine. They are incredibly powerful and flexible, allowing you to write complex SQL rules which fit your unique business needs.
Get started with our [Row Level Security Guides](/docs/guides/auth/row-level-security).
### Row Level Security
Authentication only gets you so far. When you need granular authorization rules, nothing beats PostgreSQL's [Row Level Security (RLS)](https://www.postgresql.org/docs/current/ddl-rowsecurity.html). Supabase makes it simple to turn RLS on and off.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/rls-zoom2.mp4" type="video/mp4" muted playsInline />
</video>
### Policies
[Policies](https://www.postgresql.org/docs/current/sql-createpolicy.html) are PostgreSQL's rule engine. They are incredibly powerful and flexible, allowing you to write complex SQL rules which fit your unique business needs.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/policies-zoom2.mp4" type="video/mp4" muted playsInline />
</video>
With policies, your database becomes the rules engine. Instead of repetitively filtering your queries, like this ...
```js
const loggedInUserId = 'd0714948'
let { data, error } = await supabase
.from('users')
.select('user_id, name')
.eq('user_id', loggedInUserId)
// console.log(data)
// => { id: 'd0714948', name: 'Jane' }
```
... you can simply define a rule on your database table, `auth.uid() = user_id`, and your request will return the rows which pass the rule, even when you remove the filter from your middleware:
```js
let { data, error } = await supabase.from('users').select('user_id, name')
// console.log(data)
// Still => { id: 'd0714948', name: 'Jane' }
```
### How It Works
1. A user signs up. Supabase creates a new user in the `auth.users` table.
2. Supabase returns a new JWT, which contains the user's `UUID`.
3. Every request to your database also sends the JWT.
4. Postgres inspects the JWT to determine the user making the request.
5. The user's UID can be used in policies to restrict access to rows.
Supabase provides a special function in Postgres, `auth.uid()`, which extracts the user's UID from the JWT. This is especially useful when creating policies.
## User Management
Supabase provides multiple endpoints to authenticate and manage your users:
- [Sign up](/docs/reference/javascript/auth-signup)
- [Sign in with password](/docs/reference/javascript/auth-signinwithpassword)
- [Sign in with passwordless / one-time password (OTP)](/docs/reference/javascript/auth-signinwithotp)
- [Sign in with OAuth](/docs/reference/javascript/auth-signinwithoauth)
- [Sign out](/docs/reference/javascript/auth-signout)
When users sign up, Supabase assigns them a unique ID. You can reference this ID anywhere in your database. For example, you might create a `profiles` table referencing `id` in the `auth.users` table using a `user_id` field.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/auth-zoom2.mp4" type="video/mp4" muted playsInline />
</video>
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return OldContent()
const router = useRouter()
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true' && !router.asPath.includes('overview')) {
return NewContent()
}
return <Layout meta={meta} children={children} />
}
@@ -1,74 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
import { IconPanel, GlassPanel, IconMail } from 'ui'
import Link from 'next/link'
import { PhoneLoginsItems } from '~/components/Navigation/NavigationMenu/NavigationMenu.constants'
export const meta = {
title: 'Passwordless Login',
description:
'Learn about logging in with email one-time passwords (magic links) and SMS one-time passwords.',
}
Passwordless login is a method of authentication that allows users to log in to a website or application without using a password. Instead of entering a password, the user may be asked to provide some other form of authentication, such as a one-time code sent via email or SMS, or by using a biometric factor like their fingerprint or facial recognition.
## Benefits
There are several benefits of implementing passwordless login in your applications:
1. **Improved user experience**: By eliminating the need for users to remember and enter complex passwords, passwordless login can make it easier and more convenient for users to log in to your application. This can improve the overall user experience and make it more enjoyable for users to interact with your application.
2. **Increased security**: Passwordless login can improve the security of your application by reducing the risk of password-related security breaches, such as password reuse and weak passwords. By using alternative forms of authentication, such as one-time codes or biometric factors, you can make it more difficult for unauthorized users to access your application.
3. **Reduced support burden**: Passwordless login can also help reduce the support burden for your team by eliminating the need to reset forgotten passwords or deal with other password-related issues. This can free up your team to focus on other important tasks and improve the efficiency of your operation.
## Set up Passwordless Login with Supabase Auth
Supabase supports passwordless logins through "magic links" (instead of entering a password, the user is sent
a link which they can click to log in to their account) and one-time passwords via SMS through several communications platforms. Follow the guides below to set up a passwordless login flow with Supabase Auth.
### Email One-Time Password (OTP)
<div className="grid grid-cols-2 gap-12">
</div>
<div className="grid grid-cols-3 gap-10 not-prose py-8">
<Link href={`/guides/auth/auth-magic-link`} passHref>
<a>
<IconPanel title={'Magic Link'} span="col-span-6" icon={
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 text-gold-1000">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z" />
</svg>
}>
Implement Magic Link Login in your application
</IconPanel>
</a>
</Link>
</div>
### Phone SMS One-Time Password (OTP)
<div className="grid grid-cols-12 gap-10 not-prose py-8">
{PhoneLoginsItems.map((item) => (
<Link href={`/${item.url}`} key={item.name} passHref>
<a className="col-span-4">
<IconPanel
title={item.name}
span="col-span-6"
icon={item.icon}
isDarkMode={item.isDarkMode}
>
{item.linkDescription}
</IconPanel>
</a>
</Link>
))}
</div>
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,17 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-magic-link.mdx'
export const meta = {
id: 'auth-magic-link',
title: 'Login With Magic Link',
description: 'Use Supabase to authenticate and authorize your users using magic links.',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
}
export default Page
@@ -1,17 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-messagebird.mdx'
export const meta = {
id: 'auth-messagebird',
title: 'Phone Auth with MessageBird',
description: 'How to set up and use Mobile OTP with MessageBird and Supabase.',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
}
export default Page
@@ -1,17 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-twilio.mdx'
export const meta = {
id: 'auth-twilio',
title: 'Phone Auth with Twilio',
description: 'How to set up and use Mobile OTP with Twilio and Supabase.',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
}
export default Page
@@ -1,17 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-vonage.mdx'
export const meta = {
id: 'auth-vonage',
title: 'Phone Auth with Vonage',
description: 'How to set up and use Mobile OTP with Vonage and Supabase.',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
}
export default Page
@@ -0,0 +1,47 @@
import Layout from '~/layouts/DefaultGuideLayout'
import { IconPanel, GlassPanel, IconMail } from 'ui'
import Link from 'next/link'
import { PhoneLoginsItems } from '~/components/Navigation/NavigationMenu/NavigationMenu.constants'
export const meta = {
title: 'Phone Login',
description: 'Learn about logging in to your platform using SMS one-time passwords.',
}
Phone Login is a method of authentication that allows users to log in to a website or application without using a password. Instead of entering a password, the user provides another form of authentication through a one-time code sent via SMS.
## Benefits
There are several reasons why you might want to add phone login to your applications:
- **Improved user experience**: By eliminating the need for users to remember and enter complex passwords, phone login can make it easier and more convenient for users to log in to your application. This can improve the overall user experience and make it more enjoyable for users to interact with your application.
- **Increased security**: Phone login can improve the security of your application by reducing the risk of password-related security breaches, such as password reuse and weak passwords. By using alternative forms of authentication, such as one-time codes or biometric factors, you can make it more difficult for unauthorized users to access your application.
- **Reduced support burden**: Phone login can also help reduce the support burden for your team by eliminating the need to handle password recovery flows or deal with other password-related issues. This can free up your team to focus on other important tasks and improve the efficiency of your operation.
## Set up a provider with Supabase Auth
Supabase supports Phone Login with several communications platforms. Follow the guides below to set up a provider with Supabase Auth.
<div className="grid grid-cols-12 gap-10 not-prose py-8">
{PhoneLoginsItems.map((item) => (
<Link href={`${item.url}`} key={item.name} passHref>
<a className="col-span-4">
<IconPanel
title={item.name}
span="col-span-6"
icon={item.icon}
isDarkMode={item.isDarkMode}
>
{item.linkDescription}
</IconPanel>
</a>
</Link>
))}
</div>
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+4 -4
View File
@@ -16,11 +16,11 @@ OAuth is commonly used for things like logging in to a social media account from
There are several reasons why you might want to add social login to your applications:
1. **Improved user experience**: Users can register and log in to your application using their existing social media accounts, which can be faster and more convenient than creating a new account from scratch. This makes it easier for users to access your application, improving their overall experience.
- **Improved user experience**: Users can register and log in to your application using their existing social media accounts, which can be faster and more convenient than creating a new account from scratch. This makes it easier for users to access your application, improving their overall experience.
2. **Better user engagement**: You can access additional data and insights about your users, such as their interests, demographics, and social connections. This can help you tailor your content and marketing efforts to better engage with your users and provide a more personalized experience.
- **Better user engagement**: You can access additional data and insights about your users, such as their interests, demographics, and social connections. This can help you tailor your content and marketing efforts to better engage with your users and provide a more personalized experience.
3. **Increased security**: Social login can improve the security of your application by leveraging the security measures and authentication protocols of the social media platforms that your users are logging in with. This can help protect against unauthorized access and account takeovers.
- **Increased security**: Social login can improve the security of your application by leveraging the security measures and authentication protocols of the social media platforms that your users are logging in with. This can help protect against unauthorized access and account takeovers.
## Set up a social provider with Supabase Auth
@@ -28,7 +28,7 @@ Supabase supports a suite of social providers. Follow these guides to configure
<div className="grid grid-cols-12 gap-10 not-prose py-8">
{SocialLoginItems.map((item) => (
<Link href={`/${item.url}`} key={item.name} passHref>
<Link href={`${item.url}`} key={item.name} passHref>
<a className="col-span-6 lg:col-span-4 xl:col-span-3">
<IconPanel
title={item.name}
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-apple.mdx'
export const meta = {
id: 'auth-apple',
@@ -7,11 +6,202 @@ export const meta = {
description: 'Add Apple OAuth to your Supabase project',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
To enable Apple Auth for your project, you need to set up an Apple OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Apple OAuth consists of six broad steps:
- Obtaining an `App Id` with “Sign In with Apple” capabilities.
- Obtaining a `Services Id` - this will serve as the `client_id`.
- Obtaining a `secret key` that will be used to get our `client_secret`.
- Generating the `client_secret` using the `secret key`.
- Add your `client id` and `client secret` keys to your [Supabase Project](https://app.supabase.com).
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
## Access your Apple Developer account
- Go to [developer.apple.com](https://developer.apple.com).
- Click on `Account` at the top right to log in.
![Apple Developer Portal.](/docs/img/guides/auth-apple/apple-developer-portal.png)
## Obtain an App ID
- Go to `Certificates, Identifiers & Profiles`.
- Click on `Identifiers` at the left.
- Click on the `+` sign in the upper left next to `Identifiers`.
- Select `App IDs` and click `Continue`.
- Select type `App` and click `Continue`.
- Fill out your app information:
- App description.
- Bundle ID (Apple recommends reverse-domain name style, so if your domain is acme.com and your app is called roadrunner, use: "com.acme.roadrunner").
- Scroll down and check `Sign In With Apple`.
- Click `Continue` at the top right.
- Click `Register` at the top right.
## Obtain a Services ID
This will serve as the `client_id` when you make API calls to authenticate the user.
- Go to `Certificates, Identifiers & Profiles`.
- Click on `Identifiers` at the left.
- Click on the `+` sign in the upper left next to `Identifiers`.
- Select `Services IDs` and click `Continue`.
- Fill out your information:
- App description.
- Bundle ID (you can't use the same Bundle ID from the previous step, but you can just add something to the beginning, such as "app." to make it app.com.acme.roadrunner").
- SAVE THIS ID -- this ID will become your `client_id` later.
- Click `Continue` at the top right.
- Click `Register` at the top right.
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Configure your Services ID
- Under `Identifiers`, click on your newly-created Services ID.
- Check the box next to `Sign In With Apple` to enable it.
- Click `Configure` to the right.
- Make sure your newly created Bundle ID is selected under `Primary App ID`
- Add your domain to the `Domains and Subdomains` box (do not add `https://`, just add the domain).
- In the `Return URLs` box, type the callback URL of your app which you found in the previous step and click `Next` at the bottom right.
- Click `Done` at the bottom.
- Click `Continue` at the top right.
- Click `Save` at the top right.
## Download your secret key
Now you'll need to download a `secret key` file from Apple that will be used to generate your `client_secret`.
- Go to `Certificates, Identifiers & Profiles`.
- Click on `Keys` at the left.
- Click on the `+` sign in the upper left next to `Keys`.
- Enter a `Key Name`.
- Check `Sign In with Apple`.
- Click `Configure` to the right.
- Select your newly-created Services ID from the dropdown selector.
- Click `Save` at the top right.
- Click `Continue` at the top right.
- Click `Register` at the top right.
- Click `Download` at the top right.
- Save the downloaded file -- this contains your "secret key" that will be used to generate your `client_secret`.
- Click `Done` at the top right.
## Generate a `client_secret`
The `secret key` you downloaded is used to create the `client_secret` string you'll need to authenticate your users.
According to the [Apple Docs](https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens) it needs to be a JWT
token encrypted using the Elliptic Curve Digital Signature Algorithm (ECDSA) with the P-256 curve and the SHA-256 hash algorithm.
At this time, the easiest way to generate this JWT token is with [Ruby](https://www.ruby-lang.org/en/).
If you don't have Ruby installed, you can [Download Ruby Here](https://www.ruby-lang.org/en/downloads).
- Install Ruby (or check to make sure it's installed on your system).
- Install [ruby-jwt](https://github.com/jwt/ruby-jwt).
- From the command line, run: `sudo gem install jwt`.
Create the script below using a text editor: `secret_gen.rb`
```ruby
require "jwt"
key_file = "Path to the private key"
team_id = "Your Team ID"
client_id = "The Service ID of the service you created"
key_id = "The Key ID of the private key"
validity_period = 180 # In days. Max 180 (6 months) according to Apple docs.
private_key = OpenSSL::PKey::EC.new IO.read key_file
token = JWT.encode(
{
iss: team_id,
iat: Time.now.to_i,
exp: Time.now.to_i + 86400 * validity_period,
aud: "https://appleid.apple.com",
sub: client_id
},
private_key,
"ES256",
header_fields=
{
kid: key_id
}
)
puts token
```
1. Edit the `secret_gen.rb` file:
- `key_file` = "Path to the private key you downloaded from Apple". It should look like this: `AuthKey_XXXXXXXXXX.p8`.
- `team_id` = "Your Team ID". This is found at the top right of the Apple Developer site (next to your name).
- `client_id` = "The Service ID of the service you created". This is the `Services ID` you created in the above step `Obtain a Services ID`. If you've lost this ID, you can find it in the Apple Developer Site:
- Go to `Certificates, Identifiers & Profiles`.
- Click `Identifiers` at the left.
- At the top right drop-down, select `Services IDs`.
- Find your Identifier in the list (i.e. app.com.acme.roadrunner).
- `key_id` = "The Key ID of the private key". This can be found in the name of your downloaded secret file (For a file named `AuthKey_XXXXXXXXXX.p8` your key_id is `XXXXXXXXXX`). If you've lost this ID, you can find it in the Apple Developer Site:
- Go to `Certificates, Identifiers & Profiles`.
- Click `Keys` at the left.
- Click on your newly-created key in the list.
- Look under `Key ID` to find your key_id.
2. From the command line, run: `ruby secret_gen.rb > client_secret.txt`.
3. Your `client_secret` is now stored in this `client_secret.txt` file.
## Add your OAuth credentials to Supabase
- Go to your [Supabase Dashboard](https://app.supabase.com).
- In the left sidebar, click the `Authentication` icon (near the top).
- Click `Settings` from the list to go to the `Authentication Settings` page.
- Enter the final (hosted) URL of your app under `Site URL` (this is important).
- Under `External OAuth Providers` turn `Apple Enabled` to ON.
- Enter your `client_id` and `client_secret` saved in the previous steps.
- Click `Save`.
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `apple` as the `provider`:
```js
async function signInWithApple() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'apple',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Apple Developer Account](https://developer.apple.com).
- [Ruby](https://www.ruby-lang.org/en/) Docs.
- [ruby-jwt](https://github.com/jwt/ruby-jwt) library.
- Thanks to [Janak Amarasena](https://medium.com/@janakda) who did all the heavy lifting in [How to configure Sign In with Apple](https://medium.com/identity-beyond-borders/how-to-configure-sign-in-with-apple-77c61e336003).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-azure.mdx'
export const meta = {
id: 'auth-azure',
@@ -7,11 +6,98 @@ export const meta = {
description: 'Add Azure OAuth to your Supabase project',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
To enable Azure Auth for your project, you need to set up an Azure OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Azure OAuth consists of four broad steps:
- Create an application under Azure Active Directory.
- Obtain a `Application (client) ID` with “Sign In with Azure” capabilities. This will be used as the `client id`.
- Create a `Secret ID` with “Sign In with Azure” capabilities. The value of the secret will be used as the `client secret`.
- Add the callback url of your application to the allowlist.
## Access your Azure Developer account
- Go to [portal.azure.com](https://portal.azure.com/#home).
- Login and select "Azure Active Directory" under the list of Azure Services.
## Register an application
- Under Azure Active Directory, select "App registrations" in the side panel.
- Select "New registration".
- Choose a name and select your preferred option for the supported account types.
- Specify the "Redirect URI".
- 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.](/docs/img/guides/auth-azure/azure-register-app.png)
## Obtain a Client ID
This will serve as the `client_id` when you make API calls to authenticate the user.
- 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](/docs/img/guides/auth-azure/azure-client-id.png)
## Obtain a Secret ID
This will serve as the `client_secret` when you make API calls to authenticate the user.
- Click on the name of the app registered above.
- Under "Essentials", click on "Client credentials".
- Navigate to the "Client secrets" tab and select "New client secret".
- 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](/docs/img/guides/auth-azure/azure-client-secret.png)
## Obtain the Tenant URL
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](/docs/img/guides/auth-azure/azure-tenant-url.png)
## Add login code to your client app
<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`:
```js
async function signInWithAzure() {
const { data, error } = await supabase.auth.signInWithOAuth(
{
provider: 'azure',
},
{
scopes: 'email',
}
)
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Azure Developer Account](https://portal.azure.com)
- [GitHub Discussion](https://github.com/supabase/gotrue/pull/54#issuecomment-757043573)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-bitbucket.mdx'
export const meta = {
id: 'auth-bitbucket',
@@ -7,11 +6,90 @@ export const meta = {
description: 'Add Bitbucket OAuth to your Supabase project',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
To enable Bitbucket Auth for your project, you need to set up a BitBucket OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up Bitbucket logins for your application consists of 3 parts:
- Create and configure a Bitbucket OAuth Consumer on [Bitbucket](https://bitbucket.org)
- Add your Bitbucket OAuth Consumer keys to your [Supabase Project](https://app.supabase.com)
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
## Access your Bitbucket account
- Go to [bitbucket.org](https://bitbucket.org/).
- Click on `Login` at the top right to log in.
![Bitbucket Developer Portal.](/docs/img/guides/auth-bitbucket/bitbucket-portal.png)
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create a Bitbucket OAuth app
- Click on your profile icon at the bottom left
- Click on `All Workspaces`
- Select a workspace and click on it to select it
- Click on `Settings` on the left
- Click on `OAuth consumers` on the left under `Apps and Features` (near the bottom)
- Click `Add Consumer` at the top
- Enter the name of your app under `Name`
- In `Callback URL`, type the callback URL of your app
- Check the permissions you need (Email, Read should be enough)
- Click `Save` at the bottom
- Click on your app name (the name of your new OAuth Consumer)
- Copy your `Key` (`client_key`) and `Secret` (`client_secret`) codes
## Add your Bitbucket credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
- In the left sidebar, click the `Authentication` icon (near the top)
- Click `Settings` from the list to go to the `Authentication Settings` page
- Enter the final (hosted) URL of your app under `Site URL` (this is important)
- Under `External OAuth Providers` turn `Bitbucket Enabled` to ON
- Enter your `client_id` and `client_secret` saved in the previous step
- Click `Save`
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `bitbucket` as the `provider`:
```js
async function signInWithBitbucket() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'bitbucket',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Bitbucket Account](https://bitbucket.org)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-discord.mdx'
export const meta = {
id: 'auth-discord',
@@ -7,11 +6,92 @@ export const meta = {
description: 'Add Discord OAuth to your Supabase project',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
To enable Discord Auth for your project, you need to set up a Discord Application and add the Application OAuth credentials to your Supabase Dashboard.
## Overview
Setting up Discord logins for your application consists of 3 parts:
- Create and configure a Discord Application [Discord Developer Portal](https://discord.com/developers)
- Add your Discord OAuth Consumer keys to your [Supabase Project](https://app.supabase.com)
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
## Access your Discord account
- Go to [discord.com](https://discord.com/).
- Click on `Login` at the top right to log in.
![Discord Portal.](/docs/img/guides/auth-discord/discord-portal.png)
- Once logged in, go to [discord.com/developers](https://discord.com/developers).
![Discord Portal.](/docs/img/guides/auth-discord/discord-developer-portal.png)
## Find your callback URL
In the next step you require a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create a Discord Application
- Click on `New Application` at the top right.
- Enter the name of your application and click `Create`.
- Click on `OAuth2` under `Settings` in the left side panel.
- Click `Add Redirect` under `Redirects`.
- Type or paste your `callback URL` into the `Redirects` box.
- Click `Save Changes` at the bottom.
- Copy your `Client ID` and `Client Secret` under `Client information`.
## Add your Discord credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
- In the left sidebar, click the `Authentication` icon (near the top)
- Click `Settings` from the list to go to the `Authentication Settings` page
- Enter the final (hosted) URL of your app under `Site URL` (this is important)
- Under `External OAuth Providers` turn `Discord Enabled` to ON
- Enter your `client_id` and `client_secret` saved in the previous step
- Click `Save`
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `discord` as the `provider`:
```js
async function signInWithDiscord() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'discord',
})
}
```
If your user is already signed in, Discord prompts the user again for authorization.
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Discord Account](https://discord.com)
- [Discord Developer Portal](https://discord.com/developers)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-facebook.mdx'
export const meta = {
id: 'auth-facebook',
@@ -7,11 +6,107 @@ export const meta = {
description: 'Add Facebook OAuth to your Supabase project',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
To enable Facebook Auth for your project, you need to set up a Facebook OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up Facebook logins for your application consists of 3 parts:
- Create and configure a Facebook Application on the [Facebook Developers Site](https://developers.facebook.com)
- Add your Facebook keys to your [Supabase Project](https://app.supabase.com)
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
## Access your Facebook Developer account
- Go to [developers.facebook.com](https://developers.facebook.com).
- Click on `Log In` at the top right to log in.
![Facebook Developer Portal.](/docs/img/guides/auth-facebook/facebook-portal.png)
## Create a Facebook App
- Click on `My Apps` at the top right.
- Click `Create App` near the top right.
- Select your app type and click `Continue`.
- Fill in your app information, then click `Create App`.
- This should bring you to the screen: `Add Products to Your App`. (Alternatively you can click on `Add Product` in the left sidebar to get to this screen.)
## Find your callback URI
The next step requires a callback URI, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Set up FaceBook Login for your Facebook App
From the `Add Products to your App` screen:
- Click `Setup` under `Facebook Login`
- Skip the Quickstart screen, instead, in the left sidebar, click `Settings` under `Facebook Login`
- Enter your callback URI under `Valid OAuth Redirect URIs` on the `Facebook Login Settings` page
- Enter this in the `Valid OAuth Redirect URIs` box
- Click `Save Changes` at the bottom right
Be aware that you have to set the right access levels on your Facebook App to enable 3rd party applications to read the email address.
From the `App Review -> Permissions and Features` screen:
- Click the button `Request Advanced Access` on the right side of `public_profile` and `email`
You can read more about access levels [here](https://developers.facebook.com/docs/graph-api/overview/access-levels/)
## Copy your Facebook App ID and Secret
- Click `Settings / Basic` in the left sidebar
- Copy your App ID from the top of the `Basic Settings` page
- Under `App Secret` click `Show` then copy your secret
- Make sure all required fields are completed on this screen.
## Enter your Facebook App ID and Secret into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
- In the left sidebar, click the `Authentication` icon (near the top)
- Click `Settings` from the list to go to the `Authentication Settings` page
- Enter the final (hosted) URL of your app under `Site URL` (this is important)
- Under `External OAuth Providers` turn `Facebook Enabled` to ON
- Enter your `Facebook client ID` and `Facebook secret` saved in the previous step
- Click `Save`
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `facebook` as the `provider`:
```js
async function signInWithFacebook() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'facebook',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Facebook Developers Dashboard](https://developers.facebook.com/)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-github.mdx'
export const meta = {
id: 'auth-github',
@@ -7,11 +6,100 @@ export const meta = {
description: 'Add GitHub OAuth to your Supabase project',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
To enable GitHub Auth for your project, you need to set up a GitHub OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up GitHub logins for your application consists of 3 parts:
- Create and configure a GitHub OAuth App on [GitHub](https://github.com)
- Add your GitHub OAuth keys to your [Supabase Project](https://app.supabase.com)
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
## Access your GitHub account
- Go to [github.com](https://github.com).
- Click on `Sign In` at the top right to log in.
![GitHub Developer Portal.](/docs/img/guides/auth-github/github-portal.png)
## Create a GitHub Oauth App
Go to the [GitHub Developer Settings](https://github.com/settings/developers) page:
- Click on your profile photo at the top right
- Click Settings near the bottom of the menu
- In the left sidebar, click `Developer settings` (near the bottom)
- In the left sidebar, click `OAuth Apps`
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Register a new OAuth application
- Click `Register a new application`. If you've created an app before, click `New OAuth App` here.
- In `Application name`, type the name of your app.
- In `Homepage URL`, type the full URL to your app's website.
- In `Authorization callback URL`, type the callback URL of your app.
- Enter the URL in the `Valid OAuth Redirect URIs` box.
- Click `Save Changes` at the bottom right.
- Click `Register Application`.
Copy your new OAuth credentials
- Copy and save your `Client ID`.
- Click `Generate a new client secret`.
- Copy and save your `Client secret`.
## Enter your GitHub credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
- In the left sidebar, click the `Authentication` icon (near the top)
- Click `Settings` from the list to go to the `Authentication Settings` page
- Enter the final (hosted) URL of your app under `Site URL` (this is important)
- Under `External OAuth Providers` turn `GitHub Enabled` to ON
- Enter your `GitHub Client ID` and `GitHub Client Secret` saved in the previous step
- Click `Save`
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `github` as the `provider`:
```js
async function signInWithGitHub() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [GitHub Developer Settings](https://github.com/settings/developers)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-gitlab.mdx'
export const meta = {
id: 'auth-gitlab',
@@ -7,11 +6,87 @@ export const meta = {
description: 'Add GitLab OAuth to your Supabase project',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
To enable GitLab Auth for your project, you need to set up a GitLab OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up GitLab logins for your application consists of 3 parts:
- Create and configure a GitLab Application on [GitLab](https://gitlab.com)
- Add your GitLab Application keys to your [Supabase Project](https://app.supabase.com)
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
## Access your GitLab account
- Go to [gitlab.com](https://gitlab.com).
- Click on `Login` at the top right to log in.
![GitLab Developer Portal.](/docs/img/guides/auth-gitlab/gitlab-portal.png)
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create your GitLab Application
- Click on your `profile logo` (avatar) in the top-right corner.
- Select `Edit profile`.
- In the left sidebar, select Applications.
- Enter the name of the application.
- In the `Redirect URI` box, type the callback URL of your app.
- Check the box next to `Confidential` (make sure it is checked).
- Check the scope named `read_user` (this is the only required scope).
- Click `Save Application` at the bottom.
- Copy and save your `Application ID` (`client_id`) and `Secret` (`client_secret`) which you'll need later.
## Add your GitLab credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- In the left sidebar, click the `Authentication` icon (near the top).
- Click `Settings` from the list to go to the `Authentication Settings` page.
- Enter the final (hosted) URL of your app under `Site URL` (this is important).
- Under `External OAuth Providers` turn `GitLab Enabled` to ON.
- Enter your `client_id` and `client_secret` saved in the previous step.
- Click `Save`.
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `gitlab` as the `provider`:
```js
async function signInWithGitLab() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'gitlab',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [GitLab Account](https://gitlab.com)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-google.mdx'
export const meta = {
id: 'auth-google',
@@ -7,11 +6,115 @@ export const meta = {
description: 'Add Google OAuth to your Supabase project',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
To enable Google Auth for your project, you need to set up a Google OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up Google logins for your application consists of 3 parts:
- Create and configure a Google Project on the [Google Cloud Platform Console](https://console.cloud.google.com/home/dashboard)
- Add your Google OAuth keys to your [Supabase Project](https://app.supabase.com)
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
## Access your Google Cloud Platform account
- Go to [cloud.google.com](https://cloud.google.com).
- Click on `Sign in` at the top right to log in.
![Google Developer Portal.](/docs/img/guides/auth-google/google-portal.png)
## Create a Google Cloud Platform Project
- Click on `Select a Project` at the top left.
- (Or, if a project is currently selected, click on the current project name at the top left.)
- Click `New Project` at the top right.
- Fill in your app information, then click `Create`.
- (This can take a few minutes.)
- This should bring you to the dashboard for your new project.
## Create the OAuth Keys for your project
From your project's dashboard screen:
- In the search bar at the top labeled `Search products and resources` type `OAuth`.
- Click on `OAuth consent screen` from the list of results.
- On the `OAuth consent screen` page select `External`.
- Click `Create`.
## Edit your app information
- On the `Edit app registration` page fill out your app information.
- Click `Save and continue` at the bottom.
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create your Google credentials
- Click `Credentials` at the left to go to the `Credentials` page on the Google Cloud Platform console.
- Click `Create Credentials` near the top then select `OAuth client ID`
- On the `Create OAuth client ID` page, select your application type. If you're not sure, choose `Web application`.
- Fill in your app name.
- At the bottom, under `Authorized redirect URIs` click `Add URI`.
- Enter your callback URI under `Authorized redirect URIs` at the bottom.
- Enter your callback URI in the `Valid OAuth Redirect URIs` box.
- Click `Save Changes` at the bottom right.
- Click `Create`.
Copy your new OAuth credentials
- A box will appear called `OAuth client created`.
- Copy and save the values under `Your Client ID` and `Your Client Secret`.
## Enter your Google credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
- In the left sidebar, click the `Authentication` icon (near the top)
- Click `Settings` from the list to go to the `Authentication Settings` page
- Enter the final (hosted) URL of your app under `Site URL` (this is important)
- Under `External OAuth Providers` turn `Google Enabled` to ON
- Enter your `Google Client ID` and `Google Client Secret` saved in the previous step
- Click `Save`
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `google` as the `provider`:
```js
async function signInWithGoogle() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Google Cloud Platform Console](https://console.cloud.google.com/home/dashboard)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-keycloak.mdx'
export const meta = {
id: 'auth-keycloak',
@@ -7,11 +6,87 @@ export const meta = {
description: 'Add Keycloak OAuth to your Supabase project',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
To enable Keycloak Auth for your project, you need to set up an Keycloak OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
To get started with Keycloak, you can run it in a docker container with: `docker run -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -p 8080:8080 jboss/keycloak:latest`
This guide will be assuming that you are running keycloak in a docker container as described in the command above.
Keycloak OAuth consists of five broad steps:
- Create a new client in your specified keycloak realm.
- Obtain the `issuer` from the "OpenID Endpoint Configuration". This will be used as the `Keycloak URL`.
- Ensure that the new client has the "Client Protocol" set to "openid-connect" and the "Access Type" is set to "confidential".
- The `Client ID` of the client created will be used as the `client id`.
- Obtain the `Secret` from the credentials tab which will be used as the `client secret`.
- Add the callback url of your application to your allowlist.
## Access your Keycloak Admin console
- Login by visiting [`http://localhost:8080`](http://localhost:8080) and clicking on "Administration Console".
## Create a Keycloak Realm
- Once you've logged in to the Keycloak console, you can add a realm from the side panel. The default realm should be named "Master".
- 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.](/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](/docs/img/guides/auth-keycloak/keycloak-add-client.png)
## Client Settings
After you've created the client successfully, ensure that you set the following settings:
1. The "Client Protocol" should be set to "openid-connect".
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](/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](/docs/img/guides/auth-keycloak/keycloak-client-secret.png)
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `keycloak` as the `provider`:
```js
async function signInWithKeycloak() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'keycloak',
options: {
scopes: 'openid',
},
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- You can find the keycloak openid endpoint configuration under the realm settings.
![Keycloak OpenID Endpoint Configuration](/docs/img/guides/auth-keycloak/keycloak-openid-endpoint-config.png)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-linkedin.mdx'
export const meta = {
id: 'auth-linkedin',
@@ -7,11 +6,86 @@ export const meta = {
description: 'Add LinkedIn OAuth to your Supabase project',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
To enable LinkedIn Auth for your project, you need to set up a LinkedIn OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up LinkedIn logins for your application consists of 3 parts:
- Create and configure a LinkedIn Project and App on the [LinkedIn Developer Dashboard](https://www.linkedin.com/developers/apps).
- Add your LinkedIn `client_id` and `client_secret` to your [Supabase Project](https://app.supabase.com).
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
## Access your LinkedIn Developer account
- Go to [LinkedIn Developer Dashboard](https://www.linkedin.com/developers/apps).
- Log in (if necessary.)
![LinkedIn Developer Portal](/docs/img/guides/auth-linkedin/linkedin_developers_page.png)
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create a LinkedIn OAuth app
- Go to [LinkedIn Developer Dashboard](https://www.linkedin.com/developers/apps).
- Click on `Create App` at the top right
- Enter your `LinkedIn Page` and `App Logo`
- Save your app
- Click `Auth` from the top menu
- Add your `Redirect URL` to the `Authorized Redirect URLs for your app` section
- Copy and save your newly-generated `Client ID`
- Copy and save your newly-generated `Client Secret`
## Enter your LinkedIn credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- In the left sidebar, click the `Authentication` icon (near the top).
- Click `Settings` from the list to go to the `Authentication Settings` page.
- Enter the final (hosted) URL of your app under `Site URL` (this is important).
- Under `External OAuth Providers` turn `LinkedIn Enabled` to ON.
- Enter your `client_id` and `client_secret` saved in the previous step.
- Click `Save`.
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `linkedin` as the `provider`:
```js
async function signInWithLinkedIn() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'linkedin',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [LinkedIn Developer Dashboard](https://api.LinkedIn.com/apps)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-notion.mdx'
export const meta = {
id: 'auth-notion',
@@ -7,11 +6,90 @@ export const meta = {
description: 'Add Notion OAuth to your Supabase project',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
To enable Notion Auth for your project, you need to set up a Notion Application and add the Application OAuth credentials to your Supabase Dashboard.
## Overview
Setting up Notion logins for your application consists of 3 parts:
- Create and configure a Notion Application [Notion Developer Portal](https://www.notion.so/my-integrations)
- Retrieve your OAuth client ID and OAuth client secret and add them to your [Supabase Project](https://app.supabase.com)
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
## Create your notion integration
- Go to [developers.notion.com](https://developers.notion.com/).
- Click "View my integrations" and login.
![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](/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](/docs/img/guides/auth-notion/notion-redirect-uri.png)
You can retrieve the redirect uri with the following steps:
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- Add `/auth/v1/callback` to the end of that to get your full `OAuth Redirect URI`.
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 />
</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](/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)
- Click `Settings` from the list to go to the `Authentication Settings` page
- Under `External OAuth Providers` turn `Notion Enabled` to ON
- Enter the "OAuth client ID" and "OAuth client secret" obtained in the `client id` and `client secret` fields.
- Click `Save`
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `notion` as the `provider`:
```js
async function signInWithNotion() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'notion',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Notion Account](https://notion.so)
- [Notion Developer Portal](https://www.notion.so/my-integrations)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-slack.mdx'
export const meta = {
id: 'auth-slack',
@@ -7,11 +6,99 @@ export const meta = {
description: 'Add Slack OAuth to your Supabase project',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
To enable Slack Auth for your project, you need to set up a Slack OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up Slack logins for your application consists of 3 parts:
- Create and configure a Slack Project and App on the [Slack Developer Dashboard](https://api.slack.com/apps).
- Add your Slack `API Key` and `API Secret Key` to your [Supabase Project](https://app.supabase.com).
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
## Access your Slack Developer account
- Go to [api.slack.com](https://api.slack.com/apps).
- Click on `Your Apps` at the top right to log in.
![Slack Developer Portal.](/docs/img/guides/auth-slack/slack-portal.png)
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create a Slack OAuth app
- Go to [api.slack.com](https://api.slack.com/apps).
- Click on `Create an App`
Under `Create an app...`:
- Click `From scratch`
- Type the name of your app
- Select your `Slack Workspace`
- Click `Create App`
Under `App Credentials`:
- Copy and save your newly-generated `Client ID`
- Copy and save your newly-generated `Client Secret`
- Click `Permissions`
Under `Redirect URLs`:
- Click `Add New Redirect URL`
- Paste your `Callback URL` then click `Add`
- Click `Save URLs`
## Enter your Slack credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- In the left sidebar, click the `Authentication` icon (near the top).
- Click `Settings` from the list to go to the `Authentication Settings` page.
- Enter the final (hosted) URL of your app under `Site URL` (this is important).
- Under `External OAuth Providers` turn `Slack Enabled` to ON.
- Enter your `Client ID` (`client_id`) and `Client Secret` (`client_secret`) saved in the previous step.
- Click `Save`.
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `slack` as the `provider`:
```js
async function signInWithSlack() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'slack',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Slack Developer Dashboard](https://api.slack.com/apps)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-spotify.mdx'
export const meta = {
id: 'auth-spotify',
@@ -7,11 +6,94 @@ export const meta = {
description: 'Add Spotify OAuth to your Supabase project',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
To enable Spotify Auth for your project, you need to set up a Spotify OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up Spotify logins for your application consists of 3 parts:
- Create and configure a Spotify Project and App on the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard/).
- Add your Spotify `API Key` and `API Secret Key` to your [Supabase Project](https://app.supabase.com).
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
## Access your Spotify Developer account
- Log into [Spotify](https://spotify.com)
- Access the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard)
![Spotify Developer Portal.](/docs/img/guides/auth-spotify/spotify-portal.png)
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create a Spotify OAuth app
- Log into [Spotify](https://spotify.com).
- Go to the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard)
- Click `Create an App`
- Type your `App name`
- Type your `App description`
- Check the box to agree with the `Developer TOS and Branding Guidelines`
- Click `Create`
- Save your `Client ID`
- Save your `Client Secret`
- Click `Edit Settings`
Under `Redirect URIs`:
- Paste your Supabase Callback URL in the box
- Click `Add`
- Click `Save` at the bottom
## Enter your Spotify credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- In the left sidebar, click the `Authentication` icon (near the top).
- Click `Settings` from the list to go to the `Authentication Settings` page.
- Enter the final (hosted) URL of your app under `Site URL` (this is important).
- Under `External OAuth Providers` turn `Spotify Enabled` to ON.
- Enter your `Client ID` (`client_id`) and `Client Secret` (`client_secret`) saved in the previous step.
- Click `Save`.
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `spotify` as the `provider`:
```js
async function signInWithSpotify() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'spotify',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Spotify Developer Dashboard](https://developer.spotify.com/dashboard/)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,17 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-twilio.mdx'
export const meta = {
id: 'auth-twilio',
title: 'Phone Auth with Twilio',
description: 'How to set up and use Mobile OTP with Twilio and Supabase.',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
}
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-twitch.mdx'
export const meta = {
id: 'auth-twitch',
@@ -7,11 +6,106 @@ export const meta = {
description: 'Add Twitch OAuth to your Supabase project',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
To enable Twitch Auth for your project, you need to set up a Twitch Application and add the Application OAuth credentials to your Supabase Dashboard.
## Overview
Setting up Twitch logins for your application consists of 3 parts:
- Create and configure a Twitch Application [Twitch Developer Console](https://dev.twitch.tv/console)
- Add your Twitch OAuth Consumer keys to your [Supabase Project](https://app.supabase.com)
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
## Access your Twitch Developer account
- Go to [dev.twitch.tv](https://dev.twitch.tv).
- 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](/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](/docs/img/guides/auth-twitch/twitch-console.png)
## Find your callback URL
In the next step you require a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create a Twitch Application
![Twitch Developer Console](/docs/img/guides/auth-twitch/twitch-console.png)
- Click on `+ Register Your Application` at the top right.
![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.)
- Select a category for your app.
- Check the Captcha box and click `Create`.
## Retrieve your Twitch OAuth Client ID and Client Secret
- Click `Manage` at the right of your application entry in the list.
![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](/docs/img/guides/auth-twitch/twitch-get-keys.png)
## Add your Twitch credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
- In the left sidebar, click the `Authentication` icon (near the top)
- Click `Settings` from the list to go to the `Authentication Settings` page
- Enter the final (hosted) URL of your app under `Site URL` (this is important)
- Under `External OAuth Providers` turn `Twitch Enabled` to ON
- Enter your `client_id` and `client_secret` saved in the previous step
- Click `Save`
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `twitch` as the `provider`:
```js
async function signInWithTwitch() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'twitch',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Twitch Account](https://twitch.tv)
- [Twitch Developer Console](https://dev.twitch.tv/console)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-twitter.mdx'
export const meta = {
id: 'auth-twitter',
@@ -7,11 +6,95 @@ export const meta = {
description: 'Add Twitter OAuth to your Supabase project',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
To enable Twitter Auth for your project, you need to set up a Twitter OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up Twitter logins for your application consists of 3 parts:
- Create and configure a Twitter Project and App on the [Twitter Developer Dashboard](https://developer.twitter.com/en/portal/dashboard).
- Add your Twitter `API Key` and `API Secret Key` to your [Supabase Project](https://app.supabase.com).
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
## Access your Twitter Developer account
- Go to [developer.twitter.com](https://developer.twitter.com).
- Click on `Sign in` at the top right to log in.
![Twitter Developer Portal.](/docs/img/guides/auth-twitter/twitter-portal.png)
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create a Twitter OAuth app
- Click `+ Create Project`.
- Enter your project name, click `Next`.
- Select your use case, click `Next`.
- Enter a description for your project, click `Next`.
- Enter a name for your app, click `Complete`.
- Copy and save your `API Key` (this is your `client_id`).
- Copy and save your `API Secret Key` (this is your `client_secret`).
- At the bottom, under `Next, setup your App` click the link `enable 3rd party authentication`.
- Under `App Settings`, click on the gear icon next to your app name to go to `App Settings`.
- At the bottom, next to `Authentication settings`, click `Edit`.
- Turn `Enable 3-legged OAuth` ON.
- Turn `Request email address from users` ON.
- Enter your `Callback URL`.
- Enter your `Website URL`.
- Enter your `Terms of service URL`.
- Enter your `Privacy policy URL`.
- Click `Save`.
## Enter your Twitter credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- In the left sidebar, click the `Authentication` icon (near the top).
- Click `Settings` from the list to go to the `Authentication Settings` page.
- Enter the final (hosted) URL of your app under `Site URL` (this is important).
- Under `External OAuth Providers` turn `Twitter Enabled` to ON.
- Enter your `API Key` (`client_id`) and `API Secret Key` (`client_secret`) saved in the previous step.
- Click `Save`.
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `twitter` as the `provider`:
```js
async function signInWithTwitter() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'twitter',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Twitter Developer Dashboard](https://developer.twitter.com/en/portal/dashboard)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-workos.mdx'
export const meta = {
id: 'auth-workos',
@@ -7,11 +6,88 @@ export const meta = {
description: 'Add WorkOS OAuth to your Supabase project',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
To enable WorkOS Auth for your project, you need to set up WorkOS OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
In this guide, we will cover how to use Supabase OAuth with WorkOS to implement Single-Sign-On(SSO).
The procedure consists of five broad steps:
- Create a new organization from your WorkOS Dashboard.
- Obtain the `Client ID` from the Configuration tab and configure redirect URI.
- Obtain the `WorkOS Secret` from the credentials tab.
- Connect a WorkOS Supported Identity Provider
- Add your WorkOS credentials into your Supabase project
## Create a WorkOS Organization
Log in to the dashboard and hop over to the Organizations tab to create and organization
![Create an Organization](/docs/img/guides/auth-workos/workos-create-organization.png)
## Obtain the Client ID and configure Redirect URI
Head over to the Configuration tab and configure the redirect URI.The redirect URI should look like `https://<project-ref>.supabase.co/auth/v1/callback`
Note that this is distinct from the redirect URI referred to in the Supabase dashboard
![Fetch Client ID and configure Redirect URI](/docs/img/guides/auth-workos/workos-clientid-redirect-uri.png)
## Obtain the WorkOS Secret
Head over to the API Keys page and obtain the secret key.
![WorkOS Secret Key](/docs/img/guides/auth-workos/workos-secret-key.png)
## Connect a WorkOS Supported Identity Provider
Set up the identity provider by visiting the setup link.
![Visiting the setup link](/docs/img/guides/auth-workos/workos-setup-identity-provider.png)
You can pick between any one of the many identity providers that WorkOS supports.
## Add your WorkOS credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
- In the left sidebar, click the `Authentication` icon (near the top)
- Click `Settings` from the list to go to the `Authentication Settings` page
- Under `External OAuth Providers` turn `WorkOS Enabled` to ON
- Enter the `Client ID`, `Secret`, and `WorkOS URL` saved in the previous steps. The ``WorkOS URL` setting should be set to https://api.workos.com/
- Click `Save`
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `workos` as the `provider`:
```js
async function signInWithWorkOS() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'workos',
options: {
queryParams: {
connection: '<your_connection>',
organization: '<your_organization',
provider: '<your_provider>',
},
},
})
}
```
Refer to the [WorkOS Documentation](https://workos.com/docs/reference/sso/authorize/) to learn more about the different methods.
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [WorkOS Documentation](https://workos.com/docs/sso/guide)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/auth/auth-zoom.mdx'
export const meta = {
id: 'auth-zoom',
@@ -7,11 +6,95 @@ export const meta = {
description: 'Add Zoom OAuth to your Supabase project',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
To enable Zoom Auth for your project, you need to set up a Zoom OAuth application and add the application credentials to your Supabase Dashboard.
## Overview
Setting up Zoom logins for your application consists of 3 parts:
- Create and configure a Zoom OAuth App on [Zoom App Marketplace](https://marketplace.zoom.us/)
- Add your Zoom OAuth keys to your [Supabase Project](https://app.supabase.com)
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
## Access your Zoom Developer account
- Go to [marketplace.zoom.us](https://marketplace.zoom.us/).
- Click on `Sign In` at the top right to log in.
![Zoom Developer Portal.](/docs/img/guides/auth-zoom/zoom-portal.png)
## Find your callback URL
The next step requires a callback URL, which looks like this:
`https://<project-ref>.supabase.co/auth/v1/callback`
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
- Click on the `Settings` icon at the bottom of the left sidebar.
- Click on `API` in the list.
- Under Config / URL you'll find your API URL, you can click `Copy` to copy it to the clipboard.
- 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 />
</video>
## Create a Zoom Oauth App
- Go to [marketplace.zoom.us](https://marketplace.zoom.us/).
- Click on `Sign In` at the top right to log in.
- Click `Build App` (from the dropdown Develop)
- In the OAuth card, click `Create`
- Type the name of your app
- Choose app type
- Click `Create`
Under `App credentials`
- Copy and save your `Client ID`.
- Copy and save your `Client secret`.
Under `Redirect URL for OAuth`
- Paste your `Callback URL`
- Click `Continue`
## Enter your Zoom credentials into your Supabase Project
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
- In the left sidebar, click the `Authentication` icon (near the top)
- Click `Settings` from the list to go to the `Authentication Settings` page
- Enter the final (hosted) URL of your app under `Site URL` (this is important)
- Under `External OAuth Providers` turn `Zoom Enabled` to ON
- Enter your `Zoom Client ID` and `Zoom Client Secret` saved in the previous step
- Click `Save`
## Add login code to your client app
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `zoom` as the `provider`:
```js
async function signInWithZoom() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'zoom',
})
}
```
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
```js
async function signout() {
const { error } = await supabase.auth.signOut()
}
```
## Resources
- [Supabase Account - Free Tier OK](https://supabase.com)
- [Supabase JS Client](https://github.com/supabase/supabase-js)
- [Zoom App Marketplace](https://marketplace.zoom.us/)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
-122
View File
@@ -1,122 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'cli',
title: 'Supabase CLI',
description:
'The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.',
sidebar_label: 'Overview',
toc_max_heading_level: 2,
}
The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.
You can also use the CLI to manage your Supabase projects, handle database migrations and CI/CD workflows, and generate types directly from your database schema.
## Installation
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="npm"
>
<TabPanel id="npm" label="npm">
Install the CLI as dev dependency via [npm](https://www.npmjs.com/package/supabase):
```sh
npm install supabase --save-dev
```
</TabPanel>
<TabPanel id="macos" label="macOS">
Install the CLI with [Homebrew](https://brew.sh):
```sh
brew install supabase/tap/supabase
```
</TabPanel>
<TabPanel id="windows" label="Windows">
Install the CLI with [Scoop](https://scoop.sh):
```powershell
scoop bucket add supabase https://github.com/supabase/scoop-bucket.git
scoop install supabase
```
</TabPanel>
<TabPanel id="linux" label="Linux">
The CLI is available through [Homebrew](https://brew.sh) and Linux packages.
#### Homebrew
```sh
brew install supabase/tap/supabase
```
#### Linux packages
Linux packages are provided in [Releases](https://github.com/supabase/cli/releases).
To install, download the `.apk`/`.deb`/`.rpm` file depending on your package manager
and run one of the following:
- `sudo apk add --allow-untrusted <...>.apk`
- `sudo dpkg -i <...>.deb`
- `sudo rpm -i <...>.rpm`
</TabPanel>
</Tabs>
## Updates
When a new [version](https://github.com/supabase/cli/releases) is released, you can update the CLI using the same methods.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="npm"
>
<TabPanel id="npm" label="npm">
```sh
npm update supabase --save-dev
```
</TabPanel>
<TabPanel id="macos" label="macOS">
```sh
brew upgrade supabase
```
</TabPanel>
<TabPanel id="windows" label="Windows">
```powershell
scoop update supabase
```
</TabPanel>
<TabPanel id="linux" label="Linux">
```sh
brew upgrade supabase
```
</TabPanel>
</Tabs>
## See also
- [Supabase CLI Reference](/docs/reference/cli/usage)
- [Local Development](/docs/guides/cli/local-development)
- [Managing Environments](/docs/guides/cli/managing-environments)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,261 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'local-development',
title: 'Local Development',
description: 'How to use Supabase on your local development machine.',
}
Learn how to use the Supabase CLI to develop your project locally and deploy to the Supabase Platform.
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/vyHyYpvjaks"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
## Prerequisites
Make sure you have these installed on your local machine:
- [Docker](https://docs.docker.com/engine/install/)
- [Git](https://github.com/git-guides/install-git)
- [Supabase CLI](/docs/guides/cli)
## Log in to the Supabase CLI
```bash
supabase login
```
## Initialize your project
Create a new folder for your project and start a new git repository:
```bash
# create your project folder
mkdir your-project
# move into the new folder
cd your-project
# start a new git repository
git init
```
## Start Supabase services
[Initialize](/docs/reference/cli/usage#supabase-init) Supabase to set up the configuration for developing your project locally:
```bash
supabase init
```
Make sure Docker is running. The [start](/docs/reference/cli/usage#supabase-start) command uses Docker to start the Supabase [services](/docs/architecture).
This command may take a while to run if this is the first time using the CLI.
```bash
supabase start
```
Once all of the Supabase services are running, you'll see output containing your local Supabase credentials.
You can use the [stop](/docs/reference/cli/usage#supabase-stop) command at any time to stop all services.
## Access services
You can access services directly with any Postgres client or through the API Gateway ([Kong](https://github.com/Kong/kong)).
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="postgres"
>
<TabPanel id="postgres" label="Postgres">
```sh
# Default URL:
postgresql://postgres:postgres@localhost:54322/postgres
```
The local Postgres instance can be accessed through [`psql`](https://www.postgresql.org/docs/current/app-psql.html)
or any other Postgres client, such as [pgadmin](https://www.pgadmin.org/).
For example:
```bash
psql 'postgresql://postgres:postgres@localhost:54322/postgres'
```
<Admonition type="note">
To access the database from an edge function in your local Supabase setup, replace `localhost` with `host.docker.internal`.
</Admonition>
</TabPanel>
<TabPanel id="kong" label="API Gateway">
```sh
# Default URL:
http://localhost:54321
```
If you are accessing these services without the client libraries, you may need to pass the client keys as an `Authorization` header.
Learn more about [JWT headers](/docs/learn/auth-deep-dive/auth-deep-dive-jwts).
```sh
curl 'http://localhost:54321/rest/v1/' \
-H "apikey: <anon key>" \
-H "Authorization: Bearer <anon key>"
http://localhost:54321/rest/v1/ # REST (PostgREST)
http://localhost:54321/realtime/v1/ # Realtime
http://localhost:54321/storage/v1/ # Storage
http://localhost:54321/auth/v1/ # Auth (GoTrue)
```
<Admonition type="note">
`<anon key>` is provided when you run the command `supabase start`.
</Admonition>
</TabPanel>
</Tabs>
## Database migrations
Database changes are managed through "migrations." Database migrations are a common way of tracking changes to your database over time.
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/Kx5nHBmIxyQ"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
### Make database changes
For this guide, create a table called `employees`. In Supabase Studio, navigate to the **SQL Editor** page and run the following SQL command:
```sql
create table employees (
id integer primary key generated always as identity,
name text
);
```
<Admonition type="note">
You can execute any SQL using the `DB URL` shown by [`supabase status`](/docs/reference/cli/usage#supabase-status).
</Admonition>
Run the [`db diff`](/docs/reference/cli/usage#supabase-db-diff) command to detect changes in the local database:
```sh
supabase db diff create_employees -f create_employees
```
This creates a new migration named `supabase/migrations/<timestamp>_create_employees.sql`, representing any changes made to the local database since [`supabase start`](/docs/reference/cli/usage#supabase-start).
### Add sample data
Use the seed script in `supabase/seed.sql` (created with [`supabase init`](/docs/reference/cli/usage#supabase-init)) to add sample data to the table.
```sql
-- in supabase/seed.sql
insert into public.employees (name)
values
('Erlich Backman'),
('Richard Hendricks'),
('Monica Hall');
```
Rerun the migration and seed scripts:
```bash
supabase db reset
```
You should now see the contents of `employees` in Studio.
### Reset database changes
Use the [`reset`](/docs/reference/cli/usage#supabase-db-reset) command to revert any changes to the local database.
```sql
-- run on local database to make a change
alter table employees
add department text default 'Hooli';
```
Run the following command to reset the local database:
```sh
supabase db reset
```
## Deploy your project
Go to the [Supabase Dashboard](https://app.supabase.com) and create a project to deploy the changes.
### Link your project
<Admonition type="note">
There are a few commands required to link your project. We are in the process of consolidating these commands into a single command. Bear with us!
</Admonition>
Associate your project with your remote project using [`supabase link`](/docs/reference/cli/usage#supabase-link).
```bash
supabase link --project-ref <project-id>
# You can get <project-id> from your project’s dashboard URL: https://app.supabase.com/project/<project-id>
supabase db remote commit
# Capture any changes that you have made to your database before setting up the CLI
```
`supabase/migrations` is now populated with a migration in `..._remote_commit.sql`.
This migration captures any changes required for your local database to match the schema of your remote Supabase project.
### Deploy database changes
Deploy any local database migrations using [`db push`](/docs/reference/cli/usage#supabase-db-push):
```sh
supabase db push
```
### Deploy Edge Functions
Deploy any Edge Functions using [`functions deploy`](/docs/reference/cli/usage#supabase-functions-deploy):
```sh
supabase functions deploy <function_name>
```
## Limitations
The local development environment is not as feature-complete as the Supabase Platform. Here are some of the differences:
- The Storage interface is coming soon.
- The Functions interface is coming soon.
- Logs are not supported through the interface (however you can access them through the Docker containers).
- You cannot update your project settings in the Dashboard—this must be done using the CLI.
```
```
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+82 -7
View File
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/database.mdx'
export const meta = {
id: 'database',
@@ -8,11 +7,87 @@ export const meta = {
sidebar_label: 'Overview',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
}
Every Supabase project comes with a full [Postgres](https://www.postgresql.org/) database, a free and open source
database which is considered one of the world's most stable and advanced databases.
## Postgres or PostgreSQL?
PostgreSQL the database was derived from the POSTGRES Project, a package written at the University of California at Berkeley in 1986.
This package included a query language called "PostQUEL".
In 1994, Postgres95 was built on top of POSTGRES code, adding an SQL language interpreter as a replacement for PostQUEL.
Eventually, Postgres95 was renamed to PostgreSQL to reflect the SQL query capability.
After this, many people referred to it as Postgres since it's less prone to confusion. Supabase is all about
simplicity, so we also refer to it as Postgres.
## Features
### Table View
You don't have to be a database expert to start using Supabase. Our table view makes Postgres as easy to use as a spreadsheet.
![Table View.](/docs/img/table-view.png)
### Relationships
Dig into the relationships within your data.
<video width="99%" loop="" muted="" playsInline="" controls="true">
<source src="/docs/videos/relational-drilldown-zoom.mp4" type="video/mp4" />
</video>
### Clone tables
You can duplicate your tables, just like you would inside a spreadsheet.
<video width="99%" muted playsInline controls={true}>
<source src="/docs/videos/duplicate-tables.mp4" type="video/mp4" muted playsInline />
</video>
### The SQL Editor
Supabase comes with a SQL Editor. You can also save your favorite queries to run later!
<video width="99%" muted playsInline controls={true}>
<source src="/docs/videos/favorites.mp4" type="video/mp4" muted playsInline />
</video>
### Additional features
- Supabase extends Postgres with realtime functionality using our [Realtime Server](https://github.com/supabase/realtime).
- Every project is a full Postgres database, with `postgres` level access.
- Supabase manages your database backups.
- Import data directly from a CSV or excel spreadsheet.
<Admonition type="note">
Database backups **do not** include objects stored via the Storage API, as the database only
includes metadata about these objects. Restoring an old backup does not restore objects that have
been deleted since then.
</Admonition>
### Extensions
To expand the functionality of your Postgres database, you can use extensions.
You can enable Postgres extensions with the click of a button within the Supabase dashboard.
<video width="99%" muted playsInline controls={true}>
<source src="/docs/videos/toggle-extensions.mp4" type="video/mp4" muted playsInline />
</video>
[Learn more](/docs/guides/database/extensions) about all the extensions provided on Supabase.
## Tips
Read about resetting your database password [here](/docs/guides/database/managing-passwords) and changing the timezone of your server [here](/docs/guides/database/managing-timezones).
## Next steps
- Read more about [Postgres](https://www.postgresql.org/about/)
- Sign in: [app.supabase.com](https://app.supabase.com)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+1 -2
View File
@@ -157,8 +157,7 @@ export const resources = [
title: 'Architecture',
hasLightIcon: true,
href: '/architecture',
description:
'An overview of Supabase\'s architecture and product principles.',
description: "An overview of Supabase's architecture and product principles.",
},
]
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/architecture.mdx'
export const meta = {
id: 'architecture',
@@ -7,11 +6,157 @@ export const meta = {
description: 'Supabase design and architecture',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
}
Supabase is open source. We choose open source tools which are scalable and make them simple to use.
Supabase is not a 1-to-1 mapping of Firebase. While we are building many of the features that Firebase offers, we are not going about it the same way:
our technological choices are quite different; everything we use is open source; and wherever possible, we use and support existing tools rather than developing from scratch.
Most notably, we use Postgres rather than a NoSQL store. This choice was deliberate. We believe that no other database offers the functionality required to compete with Firebase,
while maintaining the scalability required to go beyond it.
## Architecture
Each Supabase project consists of several tools:
![Supabase Architecture](/docs/img/supabase-architecture.png)
### PostgreSQL (Database)
PostgreSQL is the core of Supabase. We do not abstract the PostgreSQL database — you can access it and use it with full privileges. We simply provide tools which makes PostgreSQL as easy to use as Firebase.
- Official Docs: [postgresql.org/docs](https://www.postgresql.org/docs/current/index.html)
- Source code: [github.com/postgres/postgres](https://github.com/postgres/postgres) (mirror)
- License: [PostgreSQL Licence](https://www.postgresql.org/about/licence/)
- Language: C
### Studio (Dashboard)
An open source Dashboard for managing your database and services.
- Official Docs: [Supabase docs](/docs)
- Source code: [github.com/supabase/supabase](https://github.com/supabase/supabase/tree/master/studio)
- License: [Apache 2](https://github.com/supabase/supabase/blob/master/LICENSE)
- Language: TypeScript
### GoTrue (Auth)
A JWT-based API for managing users and issuing access tokens. This integrates with PostgreSQL's Row Level Security and the API servers.
- Official Docs: [Supabase Auth reference docs](/docs/reference/auth)
- Source code: [github.com/supabase/gotrue](https://github.com/supabase/gotrue)
- License: [MIT](https://github.com/supabase/gotrue/blob/master/LICENSE)
- Language: Go
### PostgREST (API)
A standalone web server that turns your PostgreSQL database directly into a RESTful API.
We use this with our [pg_graphql](https://github.com/supabase/pg_graphql) extension to provide a GraphQL API.
- Official Docs: [postgrest.org](https://postgrest.org/)
- Source code: [github.com/PostgREST/postgrest](https://github.com/PostgREST/postgrest)
- License: [MIT](https://github.com/PostgREST/postgrest/blob/main/LICENSE)
- Language: Haskel
### Realtime (API & multiplayer)
A scalable websocket engine for managing user Presence, broadcasting messages, and streaming database changes.
- Official Docs: [Supabase Realtime docs](/docs/guides/realtime)
- Source code: [github.com/supabase/realtime](https://github.com/supabase/realtime)
- License: [Apache 2](https://github.com/supabase/realtime/blob/master/LICENSE)
- Language: Elixir
### Storage API (large file storage)
An S3-compatible object storage service that stores metadata in Postgres.
- Official Docs: [Supabase Storage reference docs](/docs/reference/storage)
- Source code: [github.com/supabase/storage-api](https://github.com/supabase/storage-api)
- License: [Apache 2.0](https://github.com/supabase/storage-api/blob/master/LICENSE)
- Language: NodeJS / TypeScript
### Deno (Edge Functions)
A modern runtime for JavaScript and TypeScript.
- Official Docs: [deno.land](https://deno.land/)
- Source code: [github.com/denoland/deno](https://github.com/denoland/deno)
- License: [MIT](https://github.com/denoland/deno/blob/main/LICENSE.md)
- Language: TypeScript / Rust
### postgres-meta (Database management)
A RESTful API for managing your Postgres. Fetch tables, add roles, and run queries.
- Official Docs: [supabase.github.io/postgres-meta](https://supabase.github.io/postgres-meta/)
- Source code: [github.com/supabase/postgres-meta](https://github.com/supabase/postgres-meta)
- License: [Apache 2.0](https://github.com/supabase/postgres-meta/blob/master/LICENSE)
- Language: NodeJS / TypeScript
### PgBouncer
A lightweight connection pooler for PostgreSQL. This is useful for connecting to Postgres when using Serverless functions.
- Official Docs: [pgbouncer.org](http://www.pgbouncer.org/)
- Source code: [pgbouncer/pgbouncer](https://github.com/pgbouncer/pgbouncer)
- License: [ISC](https://github.com/pgbouncer/pgbouncer/blob/master/COPYRIGHT)
- Language: C
### Kong (API Gateway)
A cloud-native API gateway, built on top of Nginx.
- Official Docs: [docs.konghq.com](https://docs.konghq.com/)
- Source code: [github.com/kong/kong](https://github.com/kong/kong)
- License: [Apache 2.0](https://github.com/Kong/kong/blob/master/LICENSE)
- Language: Lua
## Product Principles
It is our goal to provide an architecture that any large-scale company would design for themselves,
and then provide tooling around that architecture that is easy-to-use for indie-developers and small teams.
We use a series of principles to ensure that scalability and usability are never mutually exclusive:
### Everything works in isolation
Each system must work as a standalone tool with as few moving parts as possible.
The litmus test for this is: "Can a user run this product with nothing but a Postgres database?"
### Everything is integrated
Supabase is composable. Even though every product works in isolation, each product on the platform needs to 10x the other products.
For integration, each tool should expose an API and Webhooks.
### Everything is extensible
We're deliberate about adding a new tool, and prefer instead to extend an existing one.
This is the opposite of many cloud providers whose product offering expands into niche use-cases. We provide _primitives_ for developers, which allow them to achieve any goal.
Less, but better.
### Everything is portable
To avoid lock-in, we make it easy to migrate in and out. Our cloud offering is compatible with our self-hosted product.
We use existing standards to increase portability (like pg_dump an CSV files). If a new standard emerges which competes with a "Supabase" approach, we will deprecate the approach in favor of the standard.
This forces us compete on experience. We aim to be the best Postgres hosting service.
### Play the long game
We sacrifice short-term wins for long-term gains. For example, it is tempting to run a fork of Postgres with additional functionality which only our customers need.
Instead, we prefer to support efforts to upstream missing functionality so that the entire community benefits. This has the additional benefit of ensuring portability and longevity.
### Build for developers
"Developers" are a specific profile of user: they are _builders_.
When assessing impact as a function of effort, developers have a large efficiency due to the type of products and systems they can build.
As the profile of a developer changes over time, Supabase will continue to evolve the product to fit this evolving profile.
### Support existing tools
Supabase supports existing tools and communities wherever possible. Supabase is more like a "community of communities" - each tool typically has its own community
which we work with.
Open source is something we approach [collaboratively](https://supabase.com/blog/supabase-series-b#giving-back): we employ maintainers, sponsor projects, invest in businesses, and develop our own open source tools.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+174 -7
View File
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/features.mdx'
export const meta = {
id: 'features',
@@ -7,11 +6,179 @@ export const meta = {
description: 'Supabase features',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
}
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-changes).
### 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 />
## Storage
### File storage
Supabase Storage makes it simple to store and serve files. [Docs](/docs/guides/storage).
### Storage CDN
Cache large files using the Supabase CDN. [Docs](/docs/guides/storage-cdn).
### Image Transformations
Transform images on the fly. [Docs](/docs/guides/storage/image-transformations).
<br />
## Edge Functions
### Deno Edge Functions
Globally distributed TypeScript functions to execute custom business logic. [Docs](/docs/guides/functions).
<br />
## Project Management
### CLI
Use our CLI to develop your project locally and deploy to the Supabase Platform. [Docs](/docs/reference/cli).
### Management 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 Changes | `GA` |
| Realtime | Broadcast | `beta` |
| Realtime | Presence | `beta` |
| Storage | | `GA` |
| Storage | S3 Backend | `GA` |
| Storage | CDN | `GA` |
| Storage | Smart CDN | `beta` |
| Storage | Image Transformations | `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` |
| Management API | | `beta` |
| CLI | | `beta` |
| Client Library: JavaScript | | `GA` |
| Client Library: Dart | | `beta` |
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/with-angular.mdx'
export const meta = {
id: 'with-angular',
@@ -8,11 +7,536 @@ export const meta = {
sidebar_label: 'Angular',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
<QuickstartIntro />
![Supabase User Management example](/docs/img/user-management-demo.png)
### GitHub
Should you get stuck while working through the guide, refer to [this repo](https://github.com/supabase/supabase/tree/master/examples/user-management/angular-user-management).
<ProjectSetup />
## Building the App
Let's start building the Angular app from scratch.
### Initialize an Angular app
We can use the [Angular CLI](https://angular.io/cli) to initialize
an app called `supabase-angular`:
```bash
npx ng new supabase-angular --routing false --style css
cd supabase-angular
```
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)
```bash
npm install @supabase/supabase-js
```
And finally we want to save the environment variables in the `environment.ts` file.
All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys).
These variables will be exposed on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
```ts title=environment.ts
export const environment = {
production: false,
supabaseUrl: 'YOUR_SUPABASE_URL',
supabaseKey: 'YOUR_SUPABASE_KEY',
}
```
Now that we have the API credentials in place, let's create a **SupabaseService** with `ng g s supabase` to initialize the Supabase client and implement functions to communicate with the Supabase API.
```ts title=src/app/supabase.service.ts
import { Injectable } from '@angular/core'
import {
AuthChangeEvent,
AuthSession,
createClient,
Session,
SupabaseClient,
User,
} from '@supabase/supabase-js'
import { environment } from 'src/environments/environment'
import { Database } from 'src/schema'
export interface Profile {
id?: string
username: string
website: string
avatar_url: string
}
@Injectable({
providedIn: 'root',
})
export class SupabaseService {
private supabase: SupabaseClient
_session: AuthSession | null = null
constructor() {
this.supabase = createClient(environment.supabaseUrl, environment.supabaseKey)
}
get session() {
this.supabase.auth.getSession().then(({ data }) => {
this._session = data.session
})
return this._session
}
profile(user: User) {
return this.supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', user.id)
.single()
}
authChanges(callback: (event: AuthChangeEvent, session: Session | null) => void) {
return this.supabase.auth.onAuthStateChange(callback)
}
signIn(email: string) {
return this.supabase.auth.signInWithOtp({ email })
}
signOut() {
return this.supabase.auth.signOut()
}
updateProfile(profile: Profile) {
const update = {
...profile,
updated_at: new Date(),
}
return this.supabase.from('profiles').upsert(update)
}
downLoadImage(path: string) {
return this.supabase.storage.from('avatars').download(path)
}
uploadAvatar(filePath: string, file: File) {
return this.supabase.storage.from('avatars').upload(filePath, file)
}
}
```
Optionally, update [src/styles.css](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/angular-user-management/src/styles.css) to style the app.
### Set up a Login component
Let's set up an Angular component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.
Create an **AuthComponent** with `ng g c auth` Angular CLI command.
```ts title=src/app/auth/auth.component.ts
import { Component, OnInit } from '@angular/core'
import { FormBuilder } from '@angular/forms'
import { SupabaseService } from '../supabase.service'
@Component({
selector: 'app-auth',
templateUrl: './auth.component.html',
styleUrls: ['./auth.component.css'],
})
export class AuthComponent implements OnInit {
loading = false
signInForm = this.formBuilder.group({
email: '',
})
constructor(
private readonly supabase: SupabaseService,
private readonly formBuilder: FormBuilder
) {}
ngOnInit(): void {}
async onSubmit(): Promise<void> {
try {
this.loading = true
const email = this.signInForm.value.email as string
const { error } = await this.supabase.signIn(email)
if (error) throw error
alert('Check your email for the login link!')
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
this.signInForm.reset()
this.loading = false
}
}
}
```
```html title=src/app/auth/auth.component.html
<div class="row flex-center flex">
<div class="col-6 form-widget" aria-live="polite">
<h1 class="header">Supabase + Angular</h1>
<p class="description">Sign in via magic link with your email below</p>
<form [formGroup]="signInForm" (ngSubmit)="onSubmit()" class="form-widget">
<div>
<label for="email">Email</label>
<input
id="email"
formControlName="email"
class="inputField"
type="email"
placeholder="Your email"
/>
</div>
<div>
<button
type="submit"
class="button block"
[disabled]="loading"
>
{{ loading ? 'Loading' : 'Send magic link' }}
</button>
</div>
</form>
</div>
</div
```
### Account page
Users also need a way to edit their profile details and manage their accounts after signing in.
Create an **AccountComponent** with the `ng g c account` Angular CLI command.
```ts title=src/app/account/account.component.ts
import { Component, Input, OnInit } from '@angular/core'
import { FormBuilder } from '@angular/forms'
import { AuthSession } from '@supabase/supabase-js'
import { Profile, SupabaseService } from '../supabase.service'
@Component({
selector: 'app-account',
templateUrl: './account.component.html',
styleUrls: ['./account.component.css'],
})
export class AccountComponent implements OnInit {
loading = false
profile!: Profile
@Input()
session!: AuthSession
updateProfileForm = this.formBuilder.group({
username: '',
website: '',
avatar_url: '',
})
constructor(private readonly supabase: SupabaseService, private formBuilder: FormBuilder) {}
async ngOnInit(): Promise<void> {
await this.getProfile()
const { username, website, avatar_url } = this.profile
this.updateProfileForm.patchValue({
username,
website,
avatar_url,
})
}
async getProfile() {
try {
this.loading = true
const { user } = this.session
let { data: profile, error, status } = await this.supabase.profile(user)
if (error && status !== 406) {
throw error
}
if (profile) {
this.profile = profile
}
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
this.loading = false
}
}
async updateProfile(): Promise<void> {
try {
this.loading = true
const { user } = this.session
const username = this.updateProfileForm.value.username as string
const website = this.updateProfileForm.value.website as string
const avatar_url = this.updateProfileForm.value.avatar_url as string
const { error } = await this.supabase.updateProfile({
id: user.id,
username,
website,
avatar_url,
})
if (error) throw error
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
this.loading = false
}
}
async signOut() {
await this.supabase.signOut()
}
}
```
```html title=src/app/account/account.component.html
<form [formGroup]="updateProfileForm" (ngSubmit)="updateProfile()" class="form-widget">
<div>
<label for="email">Email</label>
<input id="email" type="text" [value]="session.user.email" disabled />
</div>
<div>
<label for="username">Name</label>
<input formControlName="username" id="username" type="text" />
</div>
<div>
<label for="website">Website</label>
<input formControlName="website" id="website" type="url" />
</div>
<div>
<button type="submit" class="button primary block" [disabled]="loading">
{{ loading ? 'Loading ...' : 'Update' }}
</button>
</div>
<div>
<button class="button block" (click)="signOut()">Sign Out</button>
</div>
</form>
```
### Launch!
Now that we have all the components in place, let's update **AppComponent**:
```ts title=src/app/app.component.ts
import { Component, OnInit } from '@angular/core'
import { SupabaseService } from './supabase.service'
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
title = 'angular-user-management'
session = this.supabase.session
constructor(private readonly supabase: SupabaseService) {}
ngOnInit() {
this.supabase.authChanges((_, session) => (this.session = session))
}
}
```
```html title=src/app/app.component.html
<div class="container" style="padding: 50px 0 100px 0">
<app-account *ngIf="session; else auth" [session]="session"></app-account>
<ng-template #auth>
<app-auth></app-auth>
</ng-template>
</div>
```
`app.module.ts` also needs to be modified to include the `ReactiveFormsModule` from the `@angular/forms` package.
```ts title=src/app/app.module.ts
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { AppComponent } from './app.component'
import { AuthComponent } from './auth/auth.component'
import { AccountComponent } from './account/account.component'
import { ReactiveFormsModule } from '@angular/forms'
import { AvatarComponent } from './avatar/avatar.component'
@NgModule({
declarations: [AppComponent, AuthComponent, AccountComponent, AvatarComponent],
imports: [BrowserModule, ReactiveFormsModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
```
Once that's done, run this in a terminal window:
```bash
npm run start
```
And then open the browser to [localhost:4200](http://localhost:4200) and you should see the completed app.
![Supabase Angular](/docs/img/supabase-angular-demo.png)
## Bonus: Profile photos
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos.
### Create an upload widget
Let's create an avatar for the user so that they can upload a profile photo.
Create an **AvatarComponent** with `ng g c avatar` Angular CLI command.
```ts title=src/app/avatar/avatar.component.ts
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
import { SafeResourceUrl, DomSanitizer } from '@angular/platform-browser'
import { SupabaseService } from '../supabase.service'
@Component({
selector: 'app-avatar',
templateUrl: './avatar.component.html',
styleUrls: ['./avatar.component.css'],
})
export class AvatarComponent implements OnInit {
_avatarUrl: SafeResourceUrl | undefined
uploading = false
@Input()
set avatarUrl(url: string | null) {
if (url) {
this.downloadImage(url)
}
}
@Output() upload = new EventEmitter<string>()
constructor(private readonly supabase: SupabaseService, private readonly dom: DomSanitizer) {}
ngOnInit(): void {}
async downloadImage(path: string) {
try {
const { data } = await this.supabase.downLoadImage(path)
if (data instanceof Blob) {
this._avatarUrl = this.dom.bypassSecurityTrustResourceUrl(URL.createObjectURL(data))
}
} catch (error) {
if (error instanceof Error) {
console.error('Error downloading image: ', error.message)
}
}
}
async uploadAvatar(event: any) {
try {
this.uploading = true
if (!event.target.files || event.target.files.length === 0) {
throw new Error('You must select an image to upload.')
}
const file = event.target.files[0]
const fileExt = file.name.split('.').pop()
const filePath = `${Math.random()}.${fileExt}`
await this.supabase.uploadAvatar(filePath, file)
this.upload.emit(filePath)
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
this.uploading = false
}
}
}
```
```html title=src/app/avatar/avatar.component.html
<div>
<img
*ngIf="_avatarUrl"
[src]="_avatarUrl"
alt="Avatar"
class="avatar image"
style="height: 150px; width: 150px"
/>
</div>
<div *ngIf="!_avatarUrl" class="avatar no-image" style="height: 150px; width: 150px"></div>
<div style="width: 150px">
<label class="button primary block" for="single">
{{ uploading ? 'Uploading ...' : 'Upload' }}
</label>
<input
style="visibility: hidden;position: absolute"
type="file"
id="single"
accept="image/*"
(change)="uploadAvatar($event)"
[disabled]="uploading"
/>
</div>
```
### Add the new widget
And then we can add the widget on top of the **AccountComponent** html template:
```html title=src/app/account.component.html
<form [formGroup]="updateProfileForm" (ngSubmit)="updateProfile()" class="form-widget">
<app-avatar [avatarUrl]="this.avatarUrl" (upload)="updateAvatar($event)"> </app-avatar>
<!-- input fields -->
</form>
```
And add an `updateAvatar` function along with an `avatarUrl` getter to the **AccountComponent** typescript file:
```ts title=src/app/account.component.ts
@Component({
selector: 'app-account',
templateUrl: './account.component.html',
styleUrls: ['./account.component.css'],
})
export class AccountComponent implements OnInit {
// ...
get avatarUrl() {
return this.updateProfileForm.value.avatar_url as string
}
async updateAvatar(event: string): Promise<void> {
this.updateProfileForm.patchValue({
avatar_url: event,
})
await this.updateProfile()
}
// ...
}
```
## Next steps
At this stage you have a fully functional application!
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
- Sign in: [app.supabase.com](https://app.supabase.com)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/with-expo.mdx'
export const meta = {
id: 'with-expo',
@@ -9,11 +8,505 @@ export const meta = {
hide_table_of_contents: false,
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
<QuickstartIntro />
![Supabase User Management example](/docs/img/supabase-flutter-demo.png)
### GitHub
Should you get stuck while working through the guide, refer to [this repo](https://github.com/supabase/supabase/tree/master/examples/user-management/expo-user-management).
<ProjectSetup />
## Building the App
Let's start building the React Native app from scratch.
### Initialize a React Native app
We can use [`expo`](https://docs.expo.dev/get-started/create-a-new-app/) to initialize
an app called `expo-user-management`:
```bash
npx create-expo-app -t expo-template-blank-typescript expo-user-management
cd expo-user-management
```
Then let's install the additional dependencies: [supabase-js](https://github.com/supabase/supabase-js)
```bash
npm install @supabase/supabase-js
npm install react-native-elements @react-native-async-storage/async-storage react-native-url-polyfill
```
Now let's create a helper file to initialize the Supabase client.
We need the API URL and the `anon` key that you copied [earlier](#get-the-api-keys).
These variables will be exposed on the browser, and that's completely fine since we have
[Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
```ts title=lib/supabase.ts
import AsyncStorage from '@react-native-async-storage/async-storage'
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = YOUR_REACT_NATIVE_SUPABASE_URL
const supabaseAnonKey = YOUR_REACT_NATIVE_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
auth: {
storage: AsyncStorage as any,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: false,
},
})
```
### Set up a Login component
Let's set up a React Native component to manage logins and sign ups.
Users would be able to sign in with their email and password.
```tsx title=components/Auth.tsx
import React, { useState } from 'react'
import { Alert, StyleSheet, View } from 'react-native'
import { supabase } from '../lib/supabase'
import { Button, Input } from 'react-native-elements'
export default function Auth() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [loading, setLoading] = useState(false)
async function signInWithEmail() {
setLoading(true)
const { error } = await supabase.auth.signInWithPassword({
email: email,
password: password,
})
if (error) Alert.alert(error.message)
setLoading(false)
}
return <Layout meta={meta} children={children} />
async function signUpWithEmail() {
setLoading(true)
const { error } = await supabase.auth.signUp({
email: email,
password: password,
})
if (error) Alert.alert(error.message)
setLoading(false)
}
return (
<View>
<View style={[styles.verticallySpaced, styles.mt20]}>
<Input
label="Email"
leftIcon={{ type: 'font-awesome', name: 'envelope' }}
onChangeText={(text) => setEmail(text)}
value={email}
placeholder="email@address.com"
autoCapitalize={'none'}
/>
</View>
<View style={styles.verticallySpaced}>
<Input
label="Password"
leftIcon={{ type: 'font-awesome', name: 'lock' }}
onChangeText={(text) => setPassword(text)}
value={password}
secureTextEntry={true}
placeholder="Password"
autoCapitalize={'none'}
/>
</View>
<View style={[styles.verticallySpaced, styles.mt20]}>
<Button title="Sign in" disabled={loading} onPress={() => signInWithEmail()} />
</View>
<View style={styles.verticallySpaced}>
<Button title="Sign up" disabled={loading} onPress={() => signUpWithEmail()} />
</View>
</View>
)
}
const styles = StyleSheet.create({
container: {
marginTop: 40,
padding: 12,
},
verticallySpaced: {
paddingTop: 4,
paddingBottom: 4,
alignSelf: 'stretch',
},
mt20: {
marginTop: 20,
},
})
```
### Account page
After a user is signed in we can allow them to edit their profile details and manage their account.
Let's create a new component for that called `Account.tsx`.
```tsx title=components/Account.tsx
import { useState, useEffect } from 'react'
import { supabase } from '../lib/supabase'
import { StyleSheet, View, Alert } from 'react-native'
import { Button, Input } from 'react-native-elements'
import { Session } from '@supabase/supabase-js'
export default function Account({ session }: { session: Session }) {
const [loading, setLoading] = useState(true)
const [username, setUsername] = useState('')
const [website, setWebsite] = useState('')
const [avatarUrl, setAvatarUrl] = useState('')
useEffect(() => {
if (session) getProfile()
}, [session])
async function getProfile() {
try {
setLoading(true)
if (!session?.user) throw new Error('No user on the session!')
let { data, error, status } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', session?.user.id)
.single()
if (error && status !== 406) {
throw error
}
if (data) {
setUsername(data.username)
setWebsite(data.website)
setAvatarUrl(data.avatar_url)
}
} catch (error) {
if (error instanceof Error) {
Alert.alert(error.message)
}
} finally {
setLoading(false)
}
}
async function updateProfile({
username,
website,
avatar_url,
}: {
username: string
website: string
avatar_url: string
}) {
try {
setLoading(true)
if (!session?.user) throw new Error('No user on the session!')
const updates = {
id: session?.user.id,
username,
website,
avatar_url,
updated_at: new Date(),
}
let { error } = await supabase.from('profiles').upsert(updates)
if (error) {
throw error
}
} catch (error) {
if (error instanceof Error) {
Alert.alert(error.message)
}
} finally {
setLoading(false)
}
}
return (
<View>
<View style={[styles.verticallySpaced, styles.mt20]}>
<Input label="Email" value={session?.user?.email} disabled />
</View>
<View style={styles.verticallySpaced}>
<Input label="Username" value={username || ''} onChangeText={(text) => setUsername(text)} />
</View>
<View style={styles.verticallySpaced}>
<Input label="Website" value={website || ''} onChangeText={(text) => setWebsite(text)} />
</View>
<View style={[styles.verticallySpaced, styles.mt20]}>
<Button
title={loading ? 'Loading ...' : 'Update'}
onPress={() => updateProfile({ username, website, avatar_url: avatarUrl })}
disabled={loading}
/>
</View>
<View style={styles.verticallySpaced}>
<Button title="Sign Out" onPress={() => supabase.auth.signOut()} />
</View>
</View>
)
}
const styles = StyleSheet.create({
container: {
marginTop: 40,
padding: 12,
},
verticallySpaced: {
paddingTop: 4,
paddingBottom: 4,
alignSelf: 'stretch',
},
mt20: {
marginTop: 20,
},
})
```
### Launch!
Now that we have all the components in place, let's update `App.tsx`:
```tsx title=App.tsx
import 'react-native-url-polyfill/auto'
import { useState, useEffect } from 'react'
import { supabase } from './lib/supabase'
import Auth from './components/Auth'
import Account from './components/Account'
import { View } from 'react-native'
import { Session } from '@supabase/supabase-js'
export default function App() {
const [session, setSession] = useState<Session | null>(null)
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session)
})
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session)
})
}, [])
return (
<View>
{session && session.user ? <Account key={session.user.id} session={session} /> : <Auth />}
</View>
)
}
```
Once that's done, run this in a terminal window:
```bash
npm start
```
And then press the appropriate key for the environment you want to test the app in and you should see the completed app.
## Bonus: Profile photos
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like
photos and videos.
### Additional dependency installation
You will need a file picker that works on the environment you will build the project for, we will use react-native-document-picker in this example.
```bash
expo install react-native-document-picker
```
### Create an upload widget
Let's create an avatar for the user so that they can upload a profile photo.
We can start by creating a new component:
```tsx title=components/Avatar.tsx
import { useState, useEffect } from 'react'
import { supabase } from '../lib/supabase'
import { StyleSheet, View, Alert, Image, Button } from 'react-native'
import DocumentPicker, { isCancel, isInProgress, types } from 'react-native-document-picker'
interface Props {
size: number
url: string | null
onUpload: (filePath: string) => void
}
export default function Avatar({ url, size = 150, onUpload }: Props) {
const [uploading, setUploading] = useState(false)
const [avatarUrl, setAvatarUrl] = useState<string | null>(null)
const avatarSize = { height: size, width: size }
useEffect(() => {
if (url) downloadImage(url)
}, [url])
async function downloadImage(path: string) {
try {
const { data, error } = await supabase.storage.from('avatars').download(path)
if (error) {
throw error
}
const fr = new FileReader()
fr.readAsDataURL(data)
fr.onload = () => {
setAvatarUrl(fr.result as string)
}
} catch (error) {
if (error instanceof Error) {
console.log('Error downloading image: ', error.message)
}
}
}
async function uploadAvatar() {
try {
setUploading(true)
const file = await DocumentPicker.pickSingle({
presentationStyle: 'fullScreen',
copyTo: 'cachesDirectory',
type: types.images,
mode: 'open',
})
const photo = {
uri: file.fileCopyUri,
type: file.type,
name: file.name,
}
const formData = new FormData()
formData.append('file', photo)
const fileExt = file.name.split('.').pop()
const filePath = `${Math.random()}.${fileExt}`
let { error } = await supabase.storage.from('avatars').upload(filePath, formData)
if (error) {
throw error
}
onUpload(filePath)
} catch (error) {
if (isCancel(error)) {
console.warn('cancelled')
// User cancelled the picker, exit any dialogs or menus and move on
} else if (isInProgress(error)) {
console.warn('multiple pickers were opened, only the last will be considered')
} else if (error instanceof Error) {
Alert.alert(error.message)
} else {
throw error
}
} finally {
setUploading(false)
}
}
return (
<View>
{avatarUrl ? (
<Image
source={{ uri: avatarUrl }}
accessibilityLabel="Avatar"
style={[avatarSize, styles.avatar, styles.image]}
/>
) : (
<View style={[avatarSize, styles.avatar, styles.noImage]} />
)}
<View>
<Button
title={uploading ? 'Uploading ...' : 'Upload'}
onPress={uploadAvatar}
disabled={uploading}
/>
</View>
</View>
)
}
const styles = StyleSheet.create({
avatar: {
borderRadius: 5,
overflow: 'hidden',
maxWidth: '100%',
},
image: {
objectFit: 'cover',
paddingTop: 0,
},
noImage: {
backgroundColor: '#333',
border: '1px solid rgb(200, 200, 200)',
borderRadius: 5,
},
})
```
### Add the new widget
And then we can add the widget to the Account page:
```tsx title=components/Account.tsx
// Import the new component
import Avatar from './Avatar'
// ...
return (
<View>
{/* Add to the body */}
<View>
<Avatar
size={200}
url={avatarUrl}
onUpload={(url: string) => {
setAvatarUrl(url)
updateProfile({ username, website, avatar_url: url })
}}
/>
</View>
{/* ... */}
</View>
)
}
// ...
```
Now you will need to run the prebuild command to get the application working on your chosen platform.
```bash
expo prebuild
```
## Next steps
At this stage you have a fully functional application!
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
- Sign in: [app.supabase.com](https://app.supabase.com)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/with-flutter.mdx'
export const meta = {
id: 'with-flutter',
@@ -8,11 +7,796 @@ export const meta = {
sidebar_label: 'Flutter',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
<QuickstartIntro />
![Supabase User Management example](/docs/img/supabase-flutter-demo.png)
### GitHub
Should you get stuck while working through the guide, refer to [this repo](https://github.com/supabase/supabase/tree/master/examples/user-management/flutter-user-management).
<ProjectSetup />
## Building the App
Let's start building the Flutter app from scratch.
### Initialize a Flutter app
We can use [`flutter create`](https://flutter.dev/docs/get-started/test-drive) to initialize
an app called `supabase_quickstart`:
```bash
flutter create supabase_quickstart
```
Then let's install the only additional dependency: [`supabase_flutter`](https://pub.dev/packages/supabase_flutter)
Copy and paste the following line in your pubspec.yaml to install the package:
```yaml
supabase_flutter: ^1.0.0
```
Run `flutter pub get` to install the dependencies.
### Setup deep links
Now that we have the dependencies installed let's setup deep links so users who have logged in via magic link or OAuth can come back to the app.
Add `io.supabase.flutterquickstart://login-callback/` as a new [redirect URL](https://app.supabase.com/project/_/auth/url-configuration) in the Dashboard.
![Supabase console deep link setting](/docs/img/deeplink-setting.png)
That is it on Supabase's end and the rest are platform specific settings:
For Android, add an intent-filter to enable deep linking:
```xml title=android/app/src/main/AndroidManifest.xml
<manifest ...>
<!-- ... other tags -->
<application ...>
<activity ...>
<!-- ... other tags -->
<!-- Add this intent-filter for Deep Links -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with YOUR_SCHEME://YOUR_HOST -->
<data
android:scheme="io.supabase.flutterquickstart"
android:host="login-callback" />
</intent-filter>
</activity>
</application>
</manifest>
```
For iOS add CFBundleURLTypes to enable deep linking:
```xml title=ios/Runner/Info.plist"
<!-- ... other tags -->
<plist>
<dict>
<!-- ... other tags -->
<!-- Add this array for Deep Links -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>io.supabase.flutterquickstart</string>
</array>
</dict>
</array>
<!-- ... other tags -->
</dict>
</plist>
```
For web:
There are no additional configurations.
### Main function
Now that we have deep links ready let's initialize the Supabase client inside our `main` function with the API credentials that you copied [earlier](#get-the-api-keys).
These variables will be exposed on the app, and that's completely fine since we have
[Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
```dart title=lib/main.dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: 'YOUR_SUPABASE_URL',
anonKey: 'YOUR_SUPABASE_ANON_KEY',
);
runApp(MyApp());
}
```
### Setting up some constants and handy functions
Let's also create a constant file to make it easier to use Supabase client.
We will also include an extension method declaration to call `showSnackBar` with one line of code.
```dart title=lib/constants.dart
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
final supabase = Supabase.instance.client;
extension ShowSnackBar on BuildContext {
void showSnackBar({
required String message,
Color backgroundColor = Colors.white,
}) {
ScaffoldMessenger.of(this).showSnackBar(SnackBar(
content: Text(message),
backgroundColor: backgroundColor,
));
}
return <Layout meta={meta} children={children} />
void showErrorSnackBar({required String message}) {
showSnackBar(message: message, backgroundColor: Colors.red);
}
}
```
### Set up Splash Screen
Let's create a splash screen that will be shown to users right after they open the app.
This screen retrieves the current session and redirects the user accordingly.
```dart title=lib/pages/splash_page.dart
import 'package:flutter/material.dart';
import 'package:supabase_quickstart/constants.dart';
class SplashPage extends StatefulWidget {
const SplashPage({super.key});
@override
_SplashPageState createState() => _SplashPageState();
}
class _SplashPageState extends State<SplashPage> {
bool _redirectCalled = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_redirect();
}
Future<void> _redirect() async {
await Future.delayed(Duration.zero);
if (_redirectCalled || !mounted) {
return;
}
_redirectCalled = true;
final session = supabase.auth.currentSession;
if (session != null) {
Navigator.of(context).pushReplacementNamed('/account');
} else {
Navigator.of(context).pushReplacementNamed('/login');
}
}
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
}
```
### Set up a Login page
Let's create a Flutter widget to manage logins and sign ups.
We'll use Magic Links, so users can sign in with their email without using passwords.
Notice that this page sets up a listener on the user's auth state using `onAuthStateChange`.
A new event will fire when the user comes back to the app by clicking their magic link, which this page can catch and redirect the user accordingly.
```dart title=lib/pages/login_page.dart
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_quickstart/constants.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
bool _isLoading = false;
bool _redirecting = false;
late final TextEditingController _emailController;
late final StreamSubscription<AuthState> _authStateSubscription;
Future<void> _signIn() async {
setState(() {
_isLoading = true;
});
try {
await supabase.auth.signInWithOtp(
email: _emailController.text,
emailRedirectTo:
kIsWeb ? null : 'io.supabase.flutterquickstart://login-callback/',
);
if (mounted) {
context.showSnackBar(message: 'Check your email for login link!');
_emailController.clear();
}
} on AuthException catch (error) {
context.showErrorSnackBar(message: error.message);
} catch (error) {
context.showErrorSnackBar(message: 'Unexpected error occurred');
}
setState(() {
_isLoading = false;
});
}
@override
void initState() {
_emailController = TextEditingController();
_authStateSubscription = supabase.auth.onAuthStateChange.listen((data) {
if (_redirecting) return;
final session = data.session;
if (session != null) {
_redirecting = true;
Navigator.of(context).pushReplacementNamed('/account');
}
});
super.initState();
}
@override
void dispose() {
_emailController.dispose();
_authStateSubscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Sign In')),
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 12),
children: [
const Text('Sign in via the magic link with your email below'),
const SizedBox(height: 18),
TextFormField(
controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'),
),
const SizedBox(height: 18),
ElevatedButton(
onPressed: _isLoading ? null : _signIn,
child: Text(_isLoading ? 'Loading' : 'Send Magic Link'),
),
],
),
);
}
}
```
### Set up Account page
After a user is signed in we can allow them to edit their profile details and manage their account.
Let's create a new widget called `account_page.dart` for that.
```dart title=lib/pages/account_page.dart"
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_quickstart/components/avatar.dart';
import 'package:supabase_quickstart/constants.dart';
class AccountPage extends StatefulWidget {
const AccountPage({super.key});
@override
_AccountPageState createState() => _AccountPageState();
}
class _AccountPageState extends State<AccountPage> {
final _usernameController = TextEditingController();
final _websiteController = TextEditingController();
String? _avatarUrl;
var _loading = false;
/// Called once a user id is received within `onAuthenticated()`
Future<void> _getProfile() async {
setState(() {
_loading = true;
});
try {
final userId = supabase.auth.currentUser!.id;
final data = await supabase
.from('profiles')
.select()
.eq('id', userId)
.single() as Map;
_usernameController.text = (data['username'] ?? '') as String;
_websiteController.text = (data['website'] ?? '') as String;
_avatarUrl = (data['avatar_url'] ?? '') as String;
} on PostgrestException catch (error) {
context.showErrorSnackBar(message: error.message);
} catch (error) {
context.showErrorSnackBar(message: 'Unexpected exception occurred');
}
setState(() {
_loading = false;
});
}
/// Called when user taps `Update` button
Future<void> _updateProfile() async {
setState(() {
_loading = true;
});
final userName = _usernameController.text;
final website = _websiteController.text;
final user = supabase.auth.currentUser;
final updates = {
'id': user!.id,
'username': userName,
'website': website,
'updated_at': DateTime.now().toIso8601String(),
};
try {
await supabase.from('profiles').upsert(updates);
if (mounted) {
context.showSnackBar(message: 'Successfully updated profile!');
}
} on PostgrestException catch (error) {
context.showErrorSnackBar(message: error.message);
} catch (error) {
context.showErrorSnackBar(message: 'Unexpeted error occurred');
}
setState(() {
_loading = false;
});
}
Future<void> _signOut() async {
try {
await supabase.auth.signOut();
} on AuthException catch (error) {
context.showErrorSnackBar(message: error.message);
} catch (error) {
context.showErrorSnackBar(message: 'Unexpected error occurred');
}
if (mounted) {
Navigator.of(context).pushReplacementNamed('/');
}
}
@override
void initState() {
super.initState();
_getProfile();
}
@override
void dispose() {
_usernameController.dispose();
_websiteController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 12),
children: [
TextFormField(
controller: _usernameController,
decoration: const InputDecoration(labelText: 'User Name'),
),
const SizedBox(height: 18),
TextFormField(
controller: _websiteController,
decoration: const InputDecoration(labelText: 'Website'),
),
const SizedBox(height: 18),
ElevatedButton(
onPressed: _updateProfile,
child: Text(_loading ? 'Saving...' : 'Update'),
),
const SizedBox(height: 18),
TextButton(onPressed: _signOut, child: const Text('Sign Out')),
],
),
);
}
}
```
### Launch!
Now that we have all the components in place, let's update `lib/main.dart`:
```dart title=lib/main.dart
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_quickstart/pages/account_page.dart';
import 'package:supabase_quickstart/pages/login_page.dart';
import 'package:supabase_quickstart/pages/splash_page.dart';
Future<void> main() async {
await Supabase.initialize(
// TODO: Replace credentials with your own
url: 'YOUR_SUPABASE_URL',
anonKey: 'YOUR_SUPABASE_ANON_KEY',
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Supabase Flutter',
theme: ThemeData.dark().copyWith(
primaryColor: Colors.green,
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: Colors.green,
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.green,
),
),
),
initialRoute: '/',
routes: <String, WidgetBuilder>{
'/': (_) => const SplashPage(),
'/login': (_) => const LoginPage(),
'/account': (_) => const AccountPage(),
},
);
}
}
```
Once that's done, run this in a terminal window to launch on Android or iOS:
```bash
flutter run
```
Or for web, run the following command to launch it on `localhost:3000`
```bash
flutter run -d web-server --web-hostname localhost --web-port 3000
```
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
![Supabase User Management example](/docs/img/supabase-flutter-account-page.png)
## Bonus: Profile photos
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like
photos and videos.
### Making sure we have a public bucket
We will be storing the image as a publicly sharable image.
Make sure your `avatars` bucket is set to public, and if it is not, change the publicity by clicking the dot menu that appears when you hover over the bucket name.
You should see an orange `Public` badge next to your bucket name if your bucket is set to public.
### Adding image uploading feature to Account page
We will use [`image_picker`](https://pub.dev/packages/image_picker) plugin to select an image from the device.
Add the following line in your pubspec.yaml file to install `image_picker`:
```yaml
image_picker: ^0.8.4
```
Using [`image_picker`](https://pub.dev/packages/image_picker) requires some additional preparation depending on the platform.
Follow the instruction on README.md of [`image_picker`](https://pub.dev/packages/image_picker) on how to set it up for the platform you are using.
Once you are done with all of the above, it is time to dive into coding.
### Create an upload widget
Let's create an avatar for the user so that they can upload a profile photo.
We can start by creating a new component:
```dart title=lib/components/avatar.dart
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_quickstart/constants.dart';
class Avatar extends StatefulWidget {
const Avatar({
super.key,
required this.imageUrl,
required this.onUpload,
});
final String? imageUrl;
final void Function(String) onUpload;
@override
_AvatarState createState() => _AvatarState();
}
class _AvatarState extends State<Avatar> {
bool _isLoading = false;
@override
Widget build(BuildContext context) {
return Column(
children: [
if (widget.imageUrl == null || widget.imageUrl!.isEmpty)
Container(
width: 150,
height: 150,
color: Colors.grey,
child: const Center(
child: Text('No Image'),
),
)
else
Image.network(
widget.imageUrl!,
width: 150,
height: 150,
fit: BoxFit.cover,
),
ElevatedButton(
onPressed: _isLoading ? null : _upload,
child: const Text('Upload'),
),
],
);
}
Future<void> _upload() async {
final picker = ImagePicker();
final imageFile = await picker.pickImage(
source: ImageSource.gallery,
maxWidth: 300,
maxHeight: 300,
);
if (imageFile == null) {
return;
}
setState(() => _isLoading = true);
try {
final bytes = await imageFile.readAsBytes();
final fileExt = imageFile.path.split('.').last;
final fileName = '${DateTime.now().toIso8601String()}.$fileExt';
final filePath = fileName;
await supabase.storage.from('avatars').uploadBinary(
filePath,
bytes,
fileOptions: FileOptions(contentType: imageFile.mimeType),
);
final imageUrlResponse = await supabase.storage
.from('avatars')
.createSignedUrl(filePath, 60 * 60 * 24 * 365 * 10);
widget.onUpload(imageUrlResponse);
} on StorageException catch (error) {
if (mounted) {
context.showErrorSnackBar(message: error.message);
}
} catch (error) {
if (mounted) {
context.showErrorSnackBar(message: 'Unexpected error occurred');
}
}
setState(() => _isLoading = false);
}
}
```
### Add the new widget
And then we can add the widget to the Account page as well as some logic to update the `avatar_url` whenever the user uploads a new avatar.
```dart title=lib/pages/account_page.dart
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:supabase_quickstart/components/avatar.dart';
import 'package:supabase_quickstart/constants.dart';
class AccountPage extends StatefulWidget {
const AccountPage({super.key});
@override
_AccountPageState createState() => _AccountPageState();
}
class _AccountPageState extends State<AccountPage> {
final _usernameController = TextEditingController();
final _websiteController = TextEditingController();
String? _avatarUrl;
var _loading = false;
/// Called once a user id is received within `onAuthenticated()`
Future<void> _getProfile() async {
setState(() {
_loading = true;
});
try {
final userId = supabase.auth.currentUser!.id;
final data = await supabase
.from('profiles')
.select()
.eq('id', userId)
.single() as Map;
_usernameController.text = (data['username'] ?? '') as String;
_websiteController.text = (data['website'] ?? '') as String;
_avatarUrl = (data['avatar_url'] ?? '') as String;
} on PostgrestException catch (error) {
context.showErrorSnackBar(message: error.message);
} catch (error) {
context.showErrorSnackBar(message: 'Unexpected exception occurred');
}
setState(() {
_loading = false;
});
}
/// Called when user taps `Update` button
Future<void> _updateProfile() async {
setState(() {
_loading = true;
});
final userName = _usernameController.text;
final website = _websiteController.text;
final user = supabase.auth.currentUser;
final updates = {
'id': user!.id,
'username': userName,
'website': website,
'updated_at': DateTime.now().toIso8601String(),
};
try {
await supabase.from('profiles').upsert(updates);
if (mounted) {
context.showSnackBar(message: 'Successfully updated profile!');
}
} on PostgrestException catch (error) {
context.showErrorSnackBar(message: error.message);
} catch (error) {
context.showErrorSnackBar(message: 'Unexpeted error occurred');
}
setState(() {
_loading = false;
});
}
Future<void> _signOut() async {
try {
await supabase.auth.signOut();
} on AuthException catch (error) {
context.showErrorSnackBar(message: error.message);
} catch (error) {
context.showErrorSnackBar(message: 'Unexpected error occurred');
}
if (mounted) {
Navigator.of(context).pushReplacementNamed('/');
}
}
/// Called when image has been uploaded to Supabase storage from within Avatar widget
Future<void> _onUpload(String imageUrl) async {
try {
final userId = supabase.auth.currentUser!.id;
await supabase.from('profiles').upsert({
'id': userId,
'avatar_url': imageUrl,
});
if (mounted) {
context.showSnackBar(message: 'Updated your profile image!');
}
} on PostgrestException catch (error) {
context.showErrorSnackBar(message: error.message);
} catch (error) {
context.showErrorSnackBar(message: 'Unexpected error has occurred');
}
if (!mounted) {
return;
}
setState(() {
_avatarUrl = imageUrl;
});
}
@override
void initState() {
super.initState();
_getProfile();
}
@override
void dispose() {
_usernameController.dispose();
_websiteController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 12),
children: [
Avatar(
imageUrl: _avatarUrl,
onUpload: _onUpload,
),
const SizedBox(height: 18),
TextFormField(
controller: _usernameController,
decoration: const InputDecoration(labelText: 'User Name'),
),
const SizedBox(height: 18),
TextFormField(
controller: _websiteController,
decoration: const InputDecoration(labelText: 'Website'),
),
const SizedBox(height: 18),
ElevatedButton(
onPressed: _updateProfile,
child: Text(_loading ? 'Saving...' : 'Update'),
),
const SizedBox(height: 18),
TextButton(onPressed: _signOut, child: const Text('Sign Out')),
],
),
);
}
}
```
Congratulations, that is it! You have now built a fully functional user management app using Flutter and Supabase!
## See also
- [Flutter Tutorial: building a Flutter chat app](https://supabase.com/blog/flutter-tutorial-building-a-chat-app)
- [Flutter Tutorial - Part 2: Authentication and Authorization with RLS](https://supabase.com/blog/flutter-authentication-and-authorization-with-rls)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/with-ionic-angular.mdx'
export const meta = {
id: 'with-ionic-angular',
@@ -8,11 +7,532 @@ export const meta = {
sidebar_label: 'Ionic Angular',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
<QuickstartIntro />
![Supabase User Management example](/docs/img/ionic-demos/ionic-angular-account.png)
### GitHub
Should you get stuck while working through the guide, refer to [this repo](https://github.com/mhartington/supabase-ionic-angular).
<ProjectSetup />
## Building the App
Let's start building the Angular app from scratch.
### Initialize an Ionic Angular app
We can use the [Ionic CLI](https://ionicframework.com/docs/cli) to initialize
an app called `supabase-ionic-angular`:
```bash
npm install -g @ionic/cli
ionic start supabase-ionic-angular blank --type angular
cd supabase-ionic-angular
```
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)
```bash
npm install @supabase/supabase-js
```
And finally we want to save the environment variables in the `environment.ts` file.
All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys).
These variables will be exposed on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
```ts title=environment.ts
export const environment = {
production: false,
supabaseUrl: 'YOUR_SUPABASE_URL',
supabaseKey: 'YOUR_SUPABASE_KEY',
}
```
Now that we have the API credentials in place, let's create a **SupabaseService** with `ionic g s supabase` to initialize the Supabase client and implement functions to communicate with the Supabase API.
```ts title=src/app/supabase.service.ts
import { Injectable } from '@angular/core'
import { LoadingController, ToastController } from '@ionic/angular'
import { AuthChangeEvent, createClient, Session, SupabaseClient } from '@supabase/supabase-js'
import { environment } from '../environments/environment'
export interface Profile {
username: string
website: string
avatar_url: string
}
@Injectable({
providedIn: 'root',
})
export class SupabaseService {
private supabase: SupabaseClient
constructor(private loadingCtrl: LoadingController, private toastCtrl: ToastController) {
this.supabase = createClient(environment.supabaseUrl, environment.supabaseKey)
}
get user() {
return this.supabase.auth.user()
}
get session() {
return this.supabase.auth.session()
}
get profile() {
return this.supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', this.user?.id)
.single()
}
authChanges(callback: (event: AuthChangeEvent, session: Session | null) => void) {
return this.supabase.auth.onAuthStateChange(callback)
}
signIn(email: string) {
return this.supabase.auth.signIn({ email })
}
signOut() {
return this.supabase.auth.signOut()
}
updateProfile(profile: Profile) {
const update = {
...profile,
id: this.user?.id,
updated_at: new Date(),
}
return this.supabase.from('profiles').upsert(update, {
returning: 'minimal', // Don't return the value after inserting
})
}
downLoadImage(path: string) {
return this.supabase.storage.from('avatars').download(path)
}
uploadAvatar(filePath: string, file: File) {
return this.supabase.storage.from('avatars').upload(filePath, file)
}
async createNotice(message: string) {
const toast = await this.toastCtrl.create({ message, duration: 5000 })
await toast.present()
}
createLoader() {
return this.loadingCtrl.create()
}
}
```
### Set up a Login route
Let's set up an route to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.
Create an **LoginPage** with `ionic g page login` Ionic CLI command.
> This guide will show the template inline, but the example app will have templateUrls
```ts title=src/app/login/login.page.ts
import { Component, OnInit } from '@angular/core'
import { SupabaseService } from '../supabase.service'
@Component({
selector: 'app-login',
template: `
<ion-header>
<ion-toolbar>
<ion-title>Login</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div class="ion-padding">
<h1>Supabase + Ionic Angular</h1>
<p>Sign in via magic link with your email below</p>
</div>
<ion-list inset="true">
<form (ngSubmit)="handleLogin($event)">
<ion-item>
<ion-label position="stacked">Email</ion-label>
<ion-input [(ngModel)]="email" name="email" autocomplete type="email"></ion-input>
</ion-item>
<div class="ion-text-center">
<ion-button type="submit" fill="clear">Login</ion-button>
</div>
</form>
</ion-list>
</ion-content>
`,
styleUrls: ['./login.page.scss'],
})
export class LoginPage implements OnInit {
email = ''
constructor(private readonly supabase: SupabaseService) {}
ngOnInit() {}
async handleLogin(event: any) {
event.preventDefault()
const loader = await this.supabase.createLoader()
await loader.present()
try {
await this.supabase.signIn(this.email)
await loader.dismiss()
await this.supabase.createNotice('Check your email for the login link!')
} catch (error) {
await loader.dismiss()
await this.supabase.createNotice(error.error_description || error.message)
}
}
}
```
### Account page
After a user is signed in we can allow them to edit their profile details and manage their account.
Create an **AccountComponent** with `ionic g page account` Ionic CLI command.
```ts title=src/app/account.component.ts
import { Component, OnInit } from '@angular/core'
import { Router } from '@angular/router'
import { Profile, SupabaseService } from '../supabase.service'
@Component({
selector: 'app-account',
template: `
<ion-header>
<ion-toolbar>
<ion-title>Account</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<form>
<ion-item>
<ion-label position="stacked">Email</ion-label>
<ion-input type="email" [value]="session?.user?.email"></ion-input>
</ion-item>
<ion-item>
<ion-label position="stacked">Name</ion-label>
<ion-input type="text" name="username" [(ngModel)]="profile.username"></ion-input>
</ion-item>
<ion-item>
<ion-label position="stacked">Website</ion-label>
<ion-input type="url" name="website" [(ngModel)]="profile.website"></ion-input>
</ion-item>
<div class="ion-text-center">
<ion-button fill="clear" (click)="updateProfile()">Update Profile</ion-button>
</div>
</form>
<div class="ion-text-center">
<ion-button fill="clear" (click)="signOut()">Log Out</ion-button>
</div>
</ion-content>
`,
styleUrls: ['./account.page.scss'],
})
export class AccountPage implements OnInit {
profile: Profile = {
username: '',
avatar_url: '',
website: '',
}
session = this.supabase.session
constructor(private readonly supabase: SupabaseService, private router: Router) {}
ngOnInit() {
this.getProfile()
}
async getProfile() {
try {
let { data: profile, error, status } = await this.supabase.profile
if (error && status !== 406) {
throw error
}
if (profile) {
this.profile = profile
}
} catch (error) {
alert(error.message)
}
}
async updateProfile(avatar_url: string = '') {
const loader = await this.supabase.createLoader()
await loader.present()
try {
await this.supabase.updateProfile({ ...this.profile, avatar_url })
await loader.dismiss()
await this.supabase.createNotice('Profile updated!')
} catch (error) {
await this.supabase.createNotice(error.message)
}
}
async signOut() {
console.log('testing?')
await this.supabase.signOut()
this.router.navigate(['/'], { replaceUrl: true })
}
}
```
### Launch!
Now that we have all the components in place, let's update **AppComponent**:
```ts title=src/app/app.component.ts
import { Component } from '@angular/core'
import { Router } from '@angular/router'
import { SupabaseService } from './supabase.service'
@Component({
selector: 'app-root',
template: `
<ion-app>
<ion-router-outlet></ion-router-outlet>
</ion-app>
`,
styleUrls: ['app.component.scss'],
})
export class AppComponent {
constructor(private supabase: SupabaseService, private router: Router) {
this.supabase.authChanges((_, session) => {
console.log(session)
if (session?.user) {
this.router.navigate(['/account'])
}
})
}
}
```
Then update the **AppRoutingModule**
```ts title=src/app/app.ts"
import { NgModule } from '@angular/core'
import { PreloadAllModules, RouterModule, Routes } from '@angular/router'
const routes: Routes = [
{
path: '/',
loadChildren: () => import('./login/login.module').then((m) => m.LoginPageModule),
},
{
path: 'account',
loadChildren: () => import('./account/account.module').then((m) => m.AccountPageModule),
},
]
@NgModule({
imports: [
RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules,
}),
],
exports: [RouterModule],
})
export class AppRoutingModule {}
```
Once that's done, run this in a terminal window:
```bash
ionic serve
```
And the browser will auomatically open to show the app.
![Supabase Angular](/docs/img/ionic-demos/ionic-angular.png)
## Bonus: Profile photos
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos.
### Create an upload widget
Let's create an avatar for the user so that they can upload a profile photo.
First install two packages in order to interact with the user's camera.
```bash
npm install @ionic/pwa-elements @capacitor/camera
```
[CapacitorJS](https://capacitorjs.com) is a cross platform native runtime from Ionic that enables web apps to be deployed through the app store and provides access to native deavice API.
Ionic PWA elements is a companion package that will polyfill certain browser APIs that provide no user interface with custom Ionic UI.
With those packages installed we can update our `main.ts` to include an additional bootstapping call for the Ionic PWA Elements.
```ts title=src/main.ts
import { enableProdMode } from '@angular/core'
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
import { AppModule } from './app/app.module'
import { environment } from './environments/environment'
import { defineCustomElements } from '@ionic/pwa-elements/loader'
defineCustomElements(window)
if (environment.production) {
enableProdMode()
}
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.log(err))
```
Then create an **AvatarComponent** with this Ionic CLI command:
```bash
ionic g component avatar --module=/src/app/account/account.module.ts --create-module
```
```ts title=src/app/avatar.component.ts
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'
import { SupabaseService } from '../supabase.service'
import { Camera, CameraResultType } from '@capacitor/camera'
@Component({
selector: 'app-avatar',
template: `
<div class="avatar_wrapper" (click)="uploadAvatar()">
<img *ngIf="_avatarUrl; else noAvatar" [src]="_avatarUrl" />
<ng-template #noAvatar>
<ion-icon name="person" class="no-avatar"></ion-icon>
</ng-template>
</div>
`,
style: [
`
:host {
display: block;
margin: auto;
min-height: 150px;
}
:host .avatar_wrapper {
margin: 16px auto 16px;
border-radius: 50%;
overflow: hidden;
height: 150px;
aspect-ratio: 1;
background: var(--ion-color-step-50);
border: thick solid var(--ion-color-step-200);
}
:host .avatar_wrapper:hover {
cursor: pointer;
}
:host .avatar_wrapper ion-icon.no-avatar {
width: 100%;
height: 115%;
}
:host img {
display: block;
object-fit: cover;
width: 100%;
height: 100%;
}
`,
],
})
export class AvatarComponent implements OnInit {
_avatarUrl: SafeResourceUrl | undefined
uploading = false
@Input()
set avatarUrl(url: string | undefined) {
if (url) {
this.downloadImage(url)
}
}
@Output() upload = new EventEmitter<string>()
constructor(private readonly supabase: SupabaseService, private readonly dom: DomSanitizer) {}
ngOnInit() {}
async downloadImage(path: string) {
try {
const { data } = await this.supabase.downLoadImage(path)
this._avatarUrl = this.dom.bypassSecurityTrustResourceUrl(URL.createObjectURL(data))
} catch (error) {
console.error('Error downloading image: ', error.message)
}
}
async uploadAvatar() {
const loader = await this.supabase.createLoader()
try {
const photo = await Camera.getPhoto({
resultType: CameraResultType.DataUrl,
})
const file = await fetch(photo.dataUrl)
.then((res) => res.blob())
.then((blob) => new File([blob], 'my-file', { type: `image/${photo.format}` }))
const fileName = `${Math.random()}-${new Date().getTime()}.${photo.format}`
await loader.present()
await this.supabase.uploadAvatar(fileName, file)
this.upload.emit(fileName)
} catch (error) {
this.supabase.createNotice(error.message)
} finally {
loader.dismiss()
}
}
}
```
### Add the new widget
And then we can add the widget on top of the **AccountComponent** html template:
```ts title=src/app/account.component.ts
template: `
<ion-header>
<ion-toolbar>
<ion-title>Account</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<app-avatar
[avatarUrl]="this.profile?.avatar_url"
(upload)="updateProfile($event)"
></app-avatar>
<!-- input fields -->
`
```
## Next steps
At this stage you have a fully functional application!
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
- Sign in: [app.supabase.com](https://app.supabase.com)
## Resources
- [Authentication in Ionic Angular with Supabase](https://supabase.com/blog/authentication-in-ionic-angular)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/with-ionic-react.mdx'
export const meta = {
id: 'with-ionic-react',
@@ -8,11 +7,501 @@ export const meta = {
sidebar_label: 'Ionic React',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
<QuickstartIntro />
![Supabase User Management example](/docs/img/ionic-demos/ionic-angular-account.png)
### GitHub
Should you get stuck while working through the guide, refer to [this repo](https://github.com/mhartington/supabase-ionic-react).
<ProjectSetup />
## Building the App
Let's start building the React app from scratch.
### Initialize an Ionic React app
We can use the [Ionic CLI](https://ionicframework.com/docs/cli) to initialize
an app called `supabase-ionic-react`:
```bash
npm install -g @ionic/cli
ionic start supabase-ionic-react blank --type react
cd supabase-ionic-react
```
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)
```bash
npm install @supabase/supabase-js
```
And finally we want to save the environment variables in a `.env`.
All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys).
```bash title=.env
REACT_APP_SUPABASE_URL=YOUR_SUPABASE_URL
REACT_APP_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
```
Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client. These variables will be exposed
on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
```js title=src/supabaseClient.js
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = process.env.REACT_APP_SUPABASE_URL
const supabaseAnonKey = process.env.REACT_APP_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
```
### Set up a Login route
Let's set up a React component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.
```jsx title=/src/pages/Login.tsx
import { useState } from 'react';
import {
IonButton,
IonContent,
IonHeader,
IonInput,
IonItem,
IonLabel,
IonList,
IonPage,
IonTitle,
IonToolbar,
useIonToast,
useIonLoading,
} from '@ionic/react';
import { supabase } from '../supabaseClient';
export function LoginPage() {
const [email, setEmail] = useState('');
const [showLoading, hideLoading] = useIonLoading();
const [showToast ] = useIonToast();
const handleLogin = async (e: React.FormEvent<HTMLFormElement>) => {
console.log()
e.preventDefault();
await showLoading();
try {
await supabase.auth.signIn({ email });
await showToast({ message: 'Check your email for the login link!' });
} catch (e: any) {
await showToast({ message: e.error_description || e.message , duration: 5000});
} finally {
await hideLoading();
}
};
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Login</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<div className="ion-padding">
<h1>Supabase + Ionic React</h1>
<p>Sign in via magic link with your email below</p>
</div>
<IonList inset={true}>
<form onSubmit={handleLogin}>
<IonItem>
<IonLabel position="stacked">Email</IonLabel>
<IonInput
value={email}
name="email"
onIonChange={(e) => setEmail(e.detail.value ?? '')}
type="email"
></IonInput>
</IonItem>
<div className="ion-text-center">
<IonButton type="submit" fill="clear">
Login
</IonButton>
</div>
</form>
</IonList>
</IonContent>
</IonPage>
);
}
```
### Account page
After a user is signed in we can allow them to edit their profile details and manage their account.
Let's create a new component for that called `Account.tsx`.
```jsx title=src/pages/Account.tsx
import {
IonButton,
IonContent,
IonHeader,
IonInput,
IonItem,
IonLabel,
IonPage,
IonTitle,
IonToolbar,
useIonLoading,
useIonToast,
useIonRouter
} from '@ionic/react';
import { useEffect, useState } from 'react';
import { supabase } from '../supabaseClient';
export function AccountPage() {
const [showLoading, hideLoading] = useIonLoading();
const [showToast] = useIonToast();
const [session] = useState(() => supabase.auth.session());
const router = useIonRouter();
const [profile, setProfile] = useState({
username: '',
website: '',
avatar_url: '',
});
useEffect(() => {
getProfile();
}, [session]);
const getProfile = async () => {
console.log('get');
await showLoading();
try {
const user = supabase.auth.user();
let { data, error, status } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', user!.id)
.single();
if (error && status !== 406) {
throw error;
}
if (data) {
setProfile({
username: data.username,
website: data.website,
avatar_url: data.avatar_url,
});
}
} catch (error: any) {
showToast({ message: error.message, duration: 5000 });
} finally {
await hideLoading();
}
};
const signOut = async () => {
await supabase.auth.signOut();
router.push('/', 'forward', 'replace');
}
return <Layout meta={meta} children={children} />
const updateProfile = async (e?: any, avatar_url: string = '') => {
e?.preventDefault();
console.log('update ');
await showLoading();
try {
const user = supabase.auth.user();
const updates = {
id: user!.id,
...profile,
avatar_url: avatar_url,
updated_at: new Date(),
};
let { error } = await supabase.from('profiles').upsert(updates, {
returning: 'minimal', // Don't return the value after inserting
});
if (error) {
throw error;
}
} catch (error: any) {
showToast({ message: error.message, duration: 5000 });
} finally {
await hideLoading();
}
};
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Account</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<form onSubmit={updateProfile}>
<IonItem>
<IonLabel>
<p>Email</p>
<p>{session?.user?.email}</p>
</IonLabel>
</IonItem>
<IonItem>
<IonLabel position="stacked">Name</IonLabel>
<IonInput
type="text"
name="username"
value={profile.username}
onIonChange={(e) =>
setProfile({ ...profile, username: e.detail.value ?? '' })
}
></IonInput>
</IonItem>
<IonItem>
<IonLabel position="stacked">Website</IonLabel>
<IonInput
type="url"
name="website"
value={profile.website}
onIonChange={(e) =>
setProfile({ ...profile, website: e.detail.value ?? '' })
}
></IonInput>
</IonItem>
<div className="ion-text-center">
<IonButton fill="clear" type="submit">
Update Profile
</IonButton>
</div>
</form>
<div className="ion-text-center">
<IonButton fill="clear" onClick={signOut}>
Log Out
</IonButton>
</div>
</IonContent>
</IonPage>
);
}
```
### Launch!
Now that we have all the components in place, let's update `App.tsx`:
```jsx title=src/App.tsx
import { Redirect, Route } from 'react-router-dom'
import { IonApp, IonRouterOutlet, setupIonicReact } from '@ionic/react'
import { IonReactRouter } from '@ionic/react-router'
import { supabase } from './supabaseClient'
import '@ionic/react/css/ionic.bundle.css'
/* Theme variables */
import './theme/variables.css'
import { LoginPage } from './pages/Login'
import { AccountPage } from './pages/Account'
import { useEffect, useState } from 'react'
import { Session } from '@supabase/supabase-js'
setupIonicReact()
const App: React.FC = () => {
const [session, setSession] = useState < Session > null
useEffect(() => {
setSession(supabase.auth.session())
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session)
})
}, [])
return (
<IonApp>
<IonReactRouter>
<IonRouterOutlet>
<Route
exact
path="/"
render={() => {
return session ? <Redirect to="/account" /> : <LoginPage />
}}
/>
<Route exact path="/account">
<AccountPage />
</Route>
</IonRouterOutlet>
</IonReactRouter>
</IonApp>
)
}
export default App
```
Once that's done, run this in a terminal window:
```bash
ionic serve
```
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
![Supabase Ionic React](/docs/img/ionic-demos/ionic-react.png)
## Bonus: Profile photos
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos.
### Create an upload widget
First install two packages in order to interact with the user's camera.
```bash
npm install @ionic/pwa-elements @capacitor/camera
```
[CapacitorJS](https://capacitorjs.com) is a cross platform native runtime from Ionic that enables web apps to be deployed through the app store and provides access to native deavice API.
Ionic PWA elements is a companion package that will polyfill certain browser APIs that provide no user interface with custom Ionic UI.
With those packages installed we can update our `index.tsx` to include an additional bootstapping call for the Ionic PWA Elements.
```ts title=src/index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import * as serviceWorkerRegistration from './serviceWorkerRegistration'
import reportWebVitals from './reportWebVitals'
import { defineCustomElements } from '@ionic/pwa-elements/loader'
defineCustomElements(window)
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)
serviceWorkerRegistration.unregister()
reportWebVitals()
```
Then create an **AvatarComponent**.
```jsx title=src/components/Avatar.tsx
import { IonIcon } from '@ionic/react';
import { person } from 'ionicons/icons';
import { Camera, CameraResultType } from '@capacitor/camera';
import { useEffect, useState } from 'react';
import { supabase } from '../supabaseClient';
import './Avatar.css'
export function Avatar({
url,
onUpload,
}: {
url: string;
onUpload: (e: any, file: string) => Promise<void>;
}) {
const [avatarUrl, setAvatarUrl] = useState<string | undefined>();
useEffect(() => {
if (url) {
downloadImage(url);
}
}, [url]);
const uploadAvatar = async () => {
try {
const photo = await Camera.getPhoto({
resultType: CameraResultType.DataUrl,
});
const file = await fetch(photo.dataUrl!)
.then((res) => res.blob())
.then(
(blob) =>
new File([blob], 'my-file', { type: `image/${photo.format}` })
);
const fileName = `${Math.random()}-${new Date().getTime()}.${
photo.format
}`;
let { error: uploadError } = await supabase.storage
.from('avatars')
.upload(fileName, file);
if (uploadError) {
throw uploadError;
}
onUpload(null, fileName);
} catch (error) {
console.log(error);
}
};
const downloadImage = async (path: string) => {
try {
const { data, error } = await supabase.storage
.from('avatars')
.download(path);
if (error) {
throw error;
}
const url = URL.createObjectURL(data!);
setAvatarUrl(url);
} catch (error: any) {
console.log('Error downloading image: ', error.message);
}
};
return (
<div className="avatar">
<div className="avatar_wrapper" onClick={uploadAvatar}>
{avatarUrl ? (
<img src={avatarUrl} />
) : (
<IonIcon icon={person} className="no-avatar" />
)}
</div>
</div>
);
}
```
### Add the new widget
And then we can add the widget to the Account page:
```jsx title=src/pages/Account.tsx
// Import the new component
import { Avatar } from '../components/Avatar';
// ...
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Account</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<Avatar url={profile.avatar_url} onUpload={updateProfile}></Avatar>
```
## Next steps
At this stage you have a fully functional application!
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
- Sign in: [app.supabase.com](https://app.supabase.com)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/with-ionic-vue.mdx'
export const meta = {
id: 'with-ionic-vue',
@@ -8,11 +7,561 @@ export const meta = {
sidebar_label: 'Ionic Vue',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
<QuickstartIntro />
![Supabase User Management example](/docs/img/ionic-demos/ionic-angular-account.png)
### GitHub
Should you get stuck while working through the guide, refer to [this repo](https://github.com/mhartington/supabase-ionic-vue).
<ProjectSetup />
## Building the App
Let's start building the Vue app from scratch.
### Initialize an Ionic Vue app
We can use the [Ionic CLI](https://ionicframework.com/docs/cli) to initialize
an app called `supabase-ionic-vue`:
```bash
npm install -g @ionic/cli
ionic start supabase-ionic-vue blank --type vue
cd supabase-ionic-vue
```
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)
```bash
npm install @supabase/supabase-js
```
And finally we want to save the environment variables in a `.env`.
All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys).
```bash title=.env
VUE_APP_SUPABASE_URL=YOUR_SUPABASE_URL
VUE_APP_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
```
Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client. These variables will be exposed
on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
```js title=src/supabase.ts"
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = process.env.VUE_APP_SUPABASE_URL as string;
const supabaseAnonKey = process.env.VUE_APP_SUPABASE_ANON_KEY as string;
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
```
### Set up a Login route
Let's set up a Vue component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.
```html title=/src/views/Login.vue
<template>
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Login</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div class="ion-padding">
<h1>Supabase + Ionic Vue</h1>
<p>Sign in via magic link with your email below</p>
</div>
<ion-list inset="true">
<form @submit.prevent="handleLogin">
<ion-item>
<ion-label position="stacked">Email</ion-label>
<ion-input v-model="email" name="email" autocomplete type="email"></ion-input>
</ion-item>
<div class="ion-text-center">
<ion-button type="submit" fill="clear">Login</ion-button>
</div>
</form>
</ion-list>
<p>{{email}}</p>
</ion-content>
</ion-page>
</template>
<script lang="ts">
import { supabase } from '../supabase'
import {
IonContent,
IonHeader,
IonPage,
IonTitle,
IonToolbar,
IonList,
IonItem,
IonLabel,
IonInput,
IonButton,
toastController,
loadingController,
} from '@ionic/vue'
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'LoginPage',
components: {
IonContent,
IonHeader,
IonPage,
IonTitle,
IonToolbar,
IonList,
IonItem,
IonLabel,
IonInput,
IonButton,
},
setup() {
const email = ref('')
const handleLogin = async () => {
const loader = await loadingController.create({})
const toast = await toastController.create({ duration: 5000 })
try {
await loader.present()
const { error } = await supabase.auth.signIn({ email: email.value })
if (error) throw error
toast.message = 'Check your email for the login link!'
await toast.present()
} catch (error: any) {
toast.message = error.error_description || error.message
await toast.present()
} finally {
await loader.dismiss()
}
}
return { handleLogin, email }
},
})
</script>
```
### Account page
After a user is signed in we can allow them to edit their profile details and manage their account.
Let's create a new component for that called `Account.vue`.
```html title=src/views/Account.vue
<template>
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Account</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<form @submit.prevent="updateProfile">
<ion-item>
<ion-label>
<p>Email</p>
<p>{{ session?.user?.email }}</p>
</ion-label>
</ion-item>
<ion-item>
<ion-label position="stacked">Name</ion-label>
<ion-input type="text" name="username" v-model="profile.username"></ion-input>
</ion-item>
<ion-item>
<ion-label position="stacked">Website</ion-label>
<ion-input type="url" name="website" v-model="profile.website"></ion-input>
</ion-item>
<div class="ion-text-center">
<ion-button fill="clear" type="submit">Update Profile</ion-button>
</div>
</form>
<div class="ion-text-center">
<ion-button fill="clear" @click="signOut">Log Out</ion-button>
</div>
</ion-content>
</ion-page>
</template>
<script lang="ts">
import { store } from '@/store'
import { supabase } from '@/supabase'
import {
IonContent,
IonHeader,
IonPage,
IonTitle,
IonToolbar,
toastController,
loadingController,
IonInput,
IonItem,
IonButton,
IonLabel,
} from '@ionic/vue'
import { User } from '@supabase/supabase-js'
import { defineComponent, onMounted, ref } from 'vue'
export default defineComponent({
name: 'AccountPage',
components: {
IonContent,
IonHeader,
IonPage,
IonTitle,
IonToolbar,
IonInput,
IonItem,
IonButton,
IonLabel,
},
setup() {
const session = ref(supabase.auth.session())
const profile = ref({
username: '',
website: '',
avatar_url: '',
})
const user = store.user as User
async function getProfile() {
const loader = await loadingController.create({})
const toast = await toastController.create({ duration: 5000 })
await loader.present()
try {
let { data, error, status } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', user.id)
.single()
if (error && status !== 406) throw error
if (data) {
console.log(data)
profile.value = {
username: data.username,
website: data.website,
avatar_url: data.avatar_url,
}
}
} catch (error: any) {
toast.message = error.message
await toast.present()
} finally {
await loader.dismiss()
}
}
const updateProfile = async () => {
const loader = await loadingController.create({})
const toast = await toastController.create({ duration: 5000 })
try {
await loader.present()
const updates = {
id: user.id,
...profile.value,
updated_at: new Date(),
}
//
let { error } = await supabase.from('profiles').upsert(updates, {
returning: 'minimal', // Don't return the value after inserting
})
//
if (error) throw error
} catch (error: any) {
toast.message = error.message
await toast.present()
} finally {
await loader.dismiss()
}
}
async function signOut() {
const loader = await loadingController.create({})
const toast = await toastController.create({ duration: 5000 })
await loader.present()
try {
let { error } = await supabase.auth.signOut()
if (error) throw error
} catch (error: any) {
toast.message = error.message
await toast.present()
} finally {
await loader.dismiss()
}
}
onMounted(() => {
getProfile()
})
return { signOut, profile, session, updateProfile }
},
})
</script>
```
### Launch!
Now that we have all the components in place, let's update `App.vue` and our routes:
```ts title=src/router.index.ts
import { createRouter, createWebHistory } from '@ionic/vue-router'
import { RouteRecordRaw } from 'vue-router'
import LoginPage from '../views/Login.vue'
import AccountPage from '../views/Account.vue'
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Login',
component: LoginPage,
},
{
path: '/account',
name: 'Account',
component: AccountPage,
},
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
})
export default router
```
```html title=src/App.vue
<template>
<ion-app>
<ion-router-outlet />
</ion-app>
</template>
<script lang="ts">
import { IonApp, IonRouterOutlet, useIonRouter } from '@ionic/vue'
import { defineComponent } from 'vue'
import { store } from './store'
import { supabase } from './supabase'
export default defineComponent({
name: 'App',
components: {
IonApp,
IonRouterOutlet,
},
setup() {
const router = useIonRouter()
store.user = supabase.auth.user() ?? {}
supabase.auth.onAuthStateChange((_, session) => {
store.user = session?.user ?? {}
if (session?.user) {
router.replace('/account')
}
})
},
})
</script>
```
Once that's done, run this in a terminal window:
```bash
ionic serve
```
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
![Supabase Ionic Vue](/docs/img/ionic-demos/ionic-vue.png)
## Bonus: Profile photos
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos.
### Create an upload widget
First install two packages in order to interact with the user's camera.
```bash
npm install @ionic/pwa-elements @capacitor/camera
```
[CapacitorJS](https://capacitorjs.com) is a cross platform native runtime from Ionic that enables web apps to be deployed through the app store and provides access to native deavice API.
Ionic PWA elements is a companion package that will polyfill certain browser APIs that provide no user interface with custom Ionic UI.
With those packages installed we can update our `main.ts` to include an additional bootstapping call for the Ionic PWA Elements.
```ts title=src/main.tsx"
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { IonicVue } from '@ionic/vue'
/* Core CSS required for Ionic components to work properly */
import '@ionic/vue/css/ionic.bundle.css'
/* Theme variables */
import './theme/variables.css'
import { defineCustomElements } from '@ionic/pwa-elements/loader'
defineCustomElements(window)
const app = createApp(App).use(IonicVue).use(router)
router.isReady().then(() => {
app.mount('#app')
})
```
Then create an **AvatarComponent**.
```html title=src/components/Avatar.vue
<template>
<div class="avatar">
<div class="avatar_wrapper" @click="uploadAvatar">
<img v-if="avatarUrl" :src="avatarUrl" />
<ion-icon v-else name="person" class="no-avatar"></ion-icon>
</div>
</div>
</template>
<script lang="ts">
import { ref, toRefs, watch, defineComponent } from 'vue'
import { supabase } from '../supabase'
import { Camera, CameraResultType } from '@capacitor/camera'
import { IonIcon } from '@ionic/vue'
import { person } from 'ionicons/icons'
export default defineComponent({
name: 'AppAvatar',
props: { path: String },
emits: ['upload', 'update:path'],
components: { IonIcon },
setup(prop, { emit }) {
const { path } = toRefs(prop)
const avatarUrl = ref('')
const downloadImage = async () => {
try {
const { data, error } = await supabase.storage.from('avatars').download(path.value)
if (error) throw error
avatarUrl.value = URL.createObjectURL(data!)
} catch (error: any) {
console.error('Error downloading image: ', error.message)
}
}
const uploadAvatar = async () => {
try {
const photo = await Camera.getPhoto({
resultType: CameraResultType.DataUrl,
})
if (photo.dataUrl) {
const file = await fetch(photo.dataUrl)
.then((res) => res.blob())
.then((blob) => new File([blob], 'my-file', { type: `image/${photo.format}` }))
const fileName = `${Math.random()}-${new Date().getTime()}.${photo.format}`
let { error: uploadError } = await supabase.storage
.from('avatars')
.upload(fileName, file)
if (uploadError) {
throw uploadError
}
emit('update:path', fileName)
emit('upload')
}
} catch (error) {
console.log(error)
}
}
watch(path, () => {
if (path.value) downloadImage()
})
return { avatarUrl, uploadAvatar, person }
},
})
</script>
<style>
.avatar {
display: block;
margin: auto;
min-height: 150px;
}
return <Layout meta={meta} children={children} />
}
.avatar .avatar_wrapper {
margin: 16px auto 16px;
border-radius: 50%;
overflow: hidden;
height: 150px;
aspect-ratio: 1;
background: var(--ion-color-step-50);
border: thick solid var(--ion-color-step-200);
}
.avatar .avatar_wrapper:hover {
cursor: pointer;
}
.avatar .avatar_wrapper ion-icon.no-avatar {
width: 100%;
height: 115%;
}
.avatar img {
display: block;
object-fit: cover;
width: 100%;
height: 100%;
}
</style>
```
### Add the new widget
And then we can add the widget to the Account page:
```html title=src/views/Account.vue
<template>
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Account</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<avatar v-model:path="profile.avatar_url" @upload="updateProfile"></avatar>
...
</template>
<script lang="ts">
import Avatar from '../components/Avatar.vue';
export default defineComponent({
name: 'AccountPage',
components: {
Avatar,
....
}
</script>
```
## Next steps
At this stage you have a fully functional application!
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
- Sign in: [app.supabase.com](https://app.supabase.com)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import OldContent from '~/pages/guides/with-nextjs.mdx'
export const meta = {
id: 'with-nextjs',
@@ -8,11 +7,726 @@ export const meta = {
sidebar_label: 'Next.js',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return OldContent()
}
return <Layout meta={meta} children={children} />
<QuickstartIntro />
![Supabase User Management example](/docs/img/user-management-demo.png)
### Video Guide
<div class="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/0Fs96oZ4se0"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
### GitHub
Should you get stuck while working through the guide, refer to [this repo](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-ts-user-management).
<ProjectSetup />
## Building the App
Let's start building the Next.js app from scratch.
### Initialize a Next.js app
We can use [`create-next-app`](https://nextjs.org/docs/getting-started) to initialize
an app called `supabase-nextjs`:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```bash
npx create-next-app@latest --use-npm supabase-nextjs
cd supabase-nextjs
```
</TabPanel>
<TabPanel id="ts" label="TypeScript">
```bash
npx create-next-app@latest --ts --use-npm supabase-nextjs
cd supabase-nextjs
```
</TabPanel>
</Tabs>
Then install the Supabase client library: [supabase-js](https://github.com/supabase/supabase-js)
```bash
npm install @supabase/supabase-js
```
And finally we want to save the environment variables in a `.env.local`.
All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys).
```bash title=.env.local
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
```
And one optional step is to update the CSS file `styles/globals.css` to make the app look nice.
You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/nextjs-ts-user-management/styles/globals.css).
### Set up a Login component
#### Supabase Auth Helpers
Next.js is a highly versatile framework offering pre-rendering at build time (SSG), server-side rendering at request time (SSR), API routes, and middleware edge-functions.
It can be challenging to authenticate your users in all these different environments, that's why we've created the [Supabase Auth Helpers](/docs/guides/auth/auth-helpers/nextjs) to make user management and data fetching within Next.js as easy as possible.
Install the auth helpers for React and Next.js
```bash
npm install @supabase/auth-helpers-react @supabase/auth-helpers-nextjs
```
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
Wrap your `pages/_app.js` component with the `SessionContextProvider` component:
```jsx title=pages/_app.js
import '../styles/globals.css'
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { SessionContextProvider } from '@supabase/auth-helpers-react'
function MyApp({ Component, pageProps }) {
const [supabase] = useState(() => createBrowserSupabaseClient())
return (
<SessionContextProvider supabaseClient={supabase} initialSession={pageProps.initialSession}>
<Component {...pageProps} />
</SessionContextProvider>
)
}
export default MyApp
```
</TabPanel>
<TabPanel id="ts" label="TypeScript">
Wrap your `pages/_app.tsx` component with the `SessionContextProvider` component:
```jsx lines=2,8 title=pages/_app.tsx
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { SessionContextProvider, Session } from '@supabase/auth-helpers-react'
function MyApp({
Component,
pageProps,
}: AppProps<{
initialSession: Session,
}>) {
const [supabase] = useState(() => createBrowserSupabaseClient())
return (
<SessionContextProvider supabaseClient={supabase} initialSession={pageProps.initialSession}>
<Component {...pageProps} />
</SessionContextProvider>
)
}
export default MyApp
```
See the [Auth Helpers docs](/docs/guides/auth/auth-helpers/nextjs#usage-with-typescript) for more details on usage with TypeScript.
</TabPanel>
</Tabs>
#### Supabase Auth UI
We can use the [Supabase Auth UI](/docs/guides/auth/auth-helpers/auth-ui) a pre-built React component for authenticating users via OAuth, email, and magic links.
Install the Supabase Auth UI for React
```bash
npm install @supabase/auth-ui-react
```
Add the `Auth` component to your home page
```jsx title=pages/index.js
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react'
const Home = () => {
const session = useSession()
const supabase = useSupabaseClient()
return (
<div className="container" style={{ padding: '50px 0 100px 0' }}>
{!session ? (
<Auth supabaseClient={supabase} appearance={{ theme: ThemeSupa }} theme="dark" />
) : (
<p>Account page will go here.</p>
)}
</div>
)
}
export default Home
```
### Account page
After a user is signed in we can allow them to edit their profile details and manage their account.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
Let's create a new component for that called `Account.js` within a `components` folder.
```tsx title=components/Account.js
import { useState, useEffect } from 'react'
import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react'
export default function Account({ session }) {
const supabase = useSupabaseClient()
const user = useUser()
const [loading, setLoading] = useState(true)
const [username, setUsername] = useState(null)
const [website, setWebsite] = useState(null)
const [avatar_url, setAvatarUrl] = useState(null)
useEffect(() => {
getProfile()
}, [session])
async function getProfile() {
try {
setLoading(true)
let { data, error, status } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', user.id)
.single()
if (error && status !== 406) {
throw error
}
if (data) {
setUsername(data.username)
setWebsite(data.website)
setAvatarUrl(data.avatar_url)
}
} catch (error) {
alert('Error loading user data!')
console.log(error)
} finally {
setLoading(false)
}
}
async function updateProfile({ username, website, avatar_url }) {
try {
setLoading(true)
const updates = {
id: user.id,
username,
website,
avatar_url,
updated_at: new Date().toISOString(),
}
let { error } = await supabase.from('profiles').upsert(updates)
if (error) throw error
alert('Profile updated!')
} catch (error) {
alert('Error updating the data!')
console.log(error)
} finally {
setLoading(false)
}
}
return (
<div className="form-widget">
<div>
<label htmlFor="email">Email</label>
<input id="email" type="text" value={session.user.email} disabled />
</div>
<div>
<label htmlFor="username">Username</label>
<input
id="username"
type="text"
value={username || ''}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div>
<label htmlFor="website">Website</label>
<input
id="website"
type="website"
value={website || ''}
onChange={(e) => setWebsite(e.target.value)}
/>
</div>
<div>
<button
className="button primary block"
onClick={() => updateProfile({ username, website, avatar_url })}
disabled={loading}
>
{loading ? 'Loading ...' : 'Update'}
</button>
</div>
<div>
<button className="button block" onClick={() => supabase.auth.signOut()}>
Sign Out
</button>
</div>
</div>
)
}
```
</TabPanel>
<TabPanel id="ts" label="TypeScript">
Let's create a new component for that called `Account.tsx` within a `components` folder.
```tsx title=components/Account.tsx
import { useState, useEffect } from 'react'
import { useUser, useSupabaseClient, Session } from '@supabase/auth-helpers-react'
import { Database } from '../utils/database.types'
type Profiles = Database['public']['Tables']['profiles']['Row']
export default function Account({ session }: { session: Session }) {
const supabase = useSupabaseClient<Database>()
const user = useUser()
const [loading, setLoading] = useState(true)
const [username, setUsername] = useState<Profiles['username']>(null)
const [website, setWebsite] = useState<Profiles['website']>(null)
const [avatar_url, setAvatarUrl] = useState<Profiles['avatar_url']>(null)
useEffect(() => {
getProfile()
}, [session])
async function getProfile() {
try {
setLoading(true)
if (!user) throw new Error('No user')
let { data, error, status } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', user.id)
.single()
if (error && status !== 406) {
throw error
}
if (data) {
setUsername(data.username)
setWebsite(data.website)
setAvatarUrl(data.avatar_url)
}
} catch (error) {
alert('Error loading user data!')
console.log(error)
} finally {
setLoading(false)
}
}
async function updateProfile({
username,
website,
avatar_url,
}: {
username: Profiles['username']
website: Profiles['website']
avatar_url: Profiles['avatar_url']
}) {
try {
setLoading(true)
if (!user) throw new Error('No user')
const updates = {
id: user.id,
username,
website,
avatar_url,
updated_at: new Date().toISOString(),
}
let { error } = await supabase.from('profiles').upsert(updates)
if (error) throw error
alert('Profile updated!')
} catch (error) {
alert('Error updating the data!')
console.log(error)
} finally {
setLoading(false)
}
}
return (
<div className="form-widget">
<div>
<label htmlFor="email">Email</label>
<input id="email" type="text" value={session.user.email} disabled />
</div>
<div>
<label htmlFor="username">Username</label>
<input
id="username"
type="text"
value={username || ''}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div>
<label htmlFor="website">Website</label>
<input
id="website"
type="website"
value={website || ''}
onChange={(e) => setWebsite(e.target.value)}
/>
</div>
<div>
<button
className="button primary block"
onClick={() => updateProfile({ username, website, avatar_url })}
disabled={loading}
>
{loading ? 'Loading ...' : 'Update'}
</button>
</div>
<div>
<button className="button block" onClick={() => supabase.auth.signOut()}>
Sign Out
</button>
</div>
</div>
)
}
```
</TabPanel>
</Tabs>
### Launch!
Now that we have all the components in place, let's update `pages/index.js`:
```jsx lines=3,14 title=pages/index.js
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react'
import Account from '../components/Account'
const Home = () => {
const session = useSession()
const supabase = useSupabaseClient()
return (
<div className="container" style={{ padding: '50px 0 100px 0' }}>
{!session ? (
<Auth supabaseClient={supabase} appearance={{ theme: ThemeSupa }} theme="dark" />
) : (
<Account session={session} />
)}
</div>
)
}
export default Home
```
Once that's done, run this in a terminal window:
```bash
npm run dev
```
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
## Bonus: Profile photos
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like
photos and videos.
### Create an upload widget
Let's create an avatar widget for the user so that they can upload a profile photo. We can start by creating a new component:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
>
<TabPanel id="js" label="JavaScript">
```jsx title=components/Avatar.js
import React, { useEffect, useState } from 'react'
import { useSupabaseClient } from '@supabase/auth-helpers-react'
export default function Avatar({ uid, url, size, onUpload }) {
const supabase = useSupabaseClient()
const [avatarUrl, setAvatarUrl] = useState(null)
const [uploading, setUploading] = useState(false)
useEffect(() => {
if (url) downloadImage(url)
}, [url])
async function downloadImage(path) {
try {
const { data, error } = await supabase.storage.from('avatars').download(path)
if (error) {
throw error
}
const url = URL.createObjectURL(data)
setAvatarUrl(url)
} catch (error) {
console.log('Error downloading image: ', error)
}
}
const uploadAvatar = async (event) => {
try {
setUploading(true)
if (!event.target.files || event.target.files.length === 0) {
throw new Error('You must select an image to upload.')
}
const file = event.target.files[0]
const fileExt = file.name.split('.').pop()
const fileName = `${uid}.${fileExt}`
const filePath = `${fileName}`
let { error: uploadError } = await supabase.storage
.from('avatars')
.upload(filePath, file, { upsert: true })
if (uploadError) {
throw uploadError
}
onUpload(filePath)
} catch (error) {
alert('Error uploading avatar!')
console.log(error)
} finally {
setUploading(false)
}
}
return (
<div>
{avatarUrl ? (
<img
src={avatarUrl}
alt="Avatar"
className="avatar image"
style={{ height: size, width: size }}
/>
) : (
<div className="avatar no-image" style={{ height: size, width: size }} />
)}
<div style={{ width: size }}>
<label className="button primary block" htmlFor="single">
{uploading ? 'Uploading ...' : 'Upload'}
</label>
<input
style={{
visibility: 'hidden',
position: 'absolute',
}}
type="file"
id="single"
accept="image/*"
onChange={uploadAvatar}
disabled={uploading}
/>
</div>
</div>
)
}
```
</TabPanel>
<TabPanel id="ts" label="TypeScript">
```tsx title=components/Avatar.tsx
import React, { useEffect, useState } from 'react'
import { useSupabaseClient } from '@supabase/auth-helpers-react'
import { Database } from '../utils/database.types'
type Profiles = Database['public']['Tables']['profiles']['Row']
export default function Avatar({
uid,
url,
size,
onUpload,
}: {
uid: string
url: Profiles['avatar_url']
size: number
onUpload: (url: string) => void
}) {
const supabase = useSupabaseClient<Database>()
const [avatarUrl, setAvatarUrl] = useState<Profiles['avatar_url']>(null)
const [uploading, setUploading] = useState(false)
useEffect(() => {
if (url) downloadImage(url)
}, [url])
async function downloadImage(path: string) {
try {
const { data, error } = await supabase.storage.from('avatars').download(path)
if (error) {
throw error
}
const url = URL.createObjectURL(data)
setAvatarUrl(url)
} catch (error) {
console.log('Error downloading image: ', error)
}
}
const uploadAvatar: React.ChangeEventHandler<HTMLInputElement> = async (event) => {
try {
setUploading(true)
if (!event.target.files || event.target.files.length === 0) {
throw new Error('You must select an image to upload.')
}
const file = event.target.files[0]
const fileExt = file.name.split('.').pop()
const fileName = `${uid}.${fileExt}`
const filePath = `${fileName}`
let { error: uploadError } = await supabase.storage
.from('avatars')
.upload(filePath, file, { upsert: true })
if (uploadError) {
throw uploadError
}
onUpload(filePath)
} catch (error) {
alert('Error uploading avatar!')
console.log(error)
} finally {
setUploading(false)
}
}
return (
<div>
{avatarUrl ? (
<img
src={avatarUrl}
alt="Avatar"
className="avatar image"
style={{ height: size, width: size }}
/>
) : (
<div className="avatar no-image" style={{ height: size, width: size }} />
)}
<div style={{ width: size }}>
<label className="button primary block" htmlFor="single">
{uploading ? 'Uploading ...' : 'Upload'}
</label>
<input
style={{
visibility: 'hidden',
position: 'absolute',
}}
type="file"
id="single"
accept="image/*"
onChange={uploadAvatar}
disabled={uploading}
/>
</div>
</div>
)
}
```
</TabPanel>
</Tabs>
### Add the new widget
And then we can add the widget to the Account page:
```jsx title=components/Account.js
// Import the new component
import Avatar from './Avatar'
// ...
return (
<div className="form-widget">
{/* Add to the body */}
<Avatar
uid={user.id}
url={avatar_url}
size={150}
onUpload={(url) => {
setAvatarUrl(url)
updateProfile({ username, website, avatar_url: url })
}}
/>
{/* ... */}
</div>
)
```
## Next steps
At this stage you have a fully functional application!
- See the complete [example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-ts-user-management) and deploy it to Vercel.
- Explore the [pre-built Auth UI for React](/docs/guides/auth/auth-helpers/auth-ui).
- Explore the [Auth Helpers for Next.js](/docs/guides/auth/auth-helpers/nextjs).
- Explore the [Supabase Cache Helpers](https://github.com/psteinroe/supabase-cache-helpers).
- See the [Next.js Subscription Payments Starter](https://github.com/vercel/nextjs-subscription-payments) template on GitHub.
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
- Sign in: [app.supabase.com](https://app.supabase.com)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/with-nuxt-3.mdx'
export const meta = {
id: 'with-nuxt-3',
@@ -8,11 +7,426 @@ export const meta = {
sidebar_label: 'Nuxt 3',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
<QuickstartIntro />
![Supabase User Management example](/docs/img/user-management-demo.png)
### Github
Should you get stuck while working through the guide, refer to [this repo](https://github.com/supabase/supabase/tree/master/examples/user-management/nuxt3-user-management).
<ProjectSetup />
## Building the App
Let's start building the Vue 3 app from scratch.
### Initialize a Nuxt 3 app
We can use [`nuxi init`](https://v3.nuxtjs.org/getting-started/quick-start/) to create an app called `nuxt-user-management`:
```bash
npx nuxi init nuxt-user-management
cd nuxt-user-management
```
Then let's install the only additional dependency: [NuxtSupabase](https://supabase.nuxtjs.org/). We only need to import NuxtSupabase as a dev dependency.
```bash
npm install @nuxtjs/supabase --save-dev
```
And finally we want to save the environment variables in a `.env`.
All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys).
```bash title=.env
SUPABASE_URL="YOUR_SUPABASE_URL"
SUPABASE_KEY="YOUR_SUPABASE_ANON_KEY"
```
These variables will be exposed on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
Amazing thing about [NuxtSupabase](https://supabase.nuxtjs.org/) is that setting environment variables is all we need to do in order to start using Supabase.
No need to initialize Supabase. The library will take care of it automatically.
And one optional step is to update the CSS file `assets/main.css` to make the app look nice.
You can find the full contents of this file [here](https://github.com/supabase-community/nuxt3-quickstarter/blob/main/assets/main.css).
```typescript title=nuxt.config.ts
import { defineNuxtConfig } from 'nuxt'
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
modules: ['@nuxtjs/supabase'],
css: ['@/assets/main.css'],
})
```
### Set up Auth component
Let's set up a Vue component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.
```html title=/components/Auth.vue
<template>
<form class="row flex-center flex" @submit.prevent="handleLogin">
<div class="col-6 form-widget">
<h1 class="header">Supabase + Nuxt 3</h1>
<p class="description">Sign in via magic link with your email below</p>
<div>
<input class="inputField" type="email" placeholder="Your email" v-model="email" />
</div>
<div>
<input
type="submit"
class="button block"
:value="loading ? 'Loading' : 'Send magic link'"
:disabled="loading"
/>
</div>
</div>
</form>
</template>
<script setup>
const supabase = useSupabaseClient()
const loading = ref(false)
const email = ref('')
const handleLogin = async () => {
try {
loading.value = true
const { error } = await supabase.auth.signInWithOtp({ email: email.value })
if (error) throw error
alert('Check your email for the login link!')
} catch (error) {
alert(error.error_description || error.message)
} finally {
loading.value = false
}
}
return <Layout meta={meta} children={children} />
}
</script>
```
### User state
To access the user information, use the composable [useSupabaseUser](https://supabase.nuxtjs.org/usage/composables/use-supabase-user) provided by the Supabase Nuxt module.
### Account component
After a user is signed in we can allow them to edit their profile details and manage their account.
Let's create a new component for that called `Account.vue`.
```html title=components/Account.vue
<template>
<form class="form-widget" @submit.prevent="updateProfile">
<div>
<label for="email">Email</label>
<input id="email" type="text" :value="user.email" disabled />
</div>
<div>
<label for="username">Username</label>
<input id="username" type="text" v-model="username" />
</div>
<div>
<label for="website">Website</label>
<input id="website" type="website" v-model="website" />
</div>
<div>
<input
type="submit"
class="button primary block"
:value="loading ? 'Loading ...' : 'Update'"
:disabled="loading"
/>
</div>
<div>
<button class="button block" @click="signOut" :disabled="loading">Sign Out</button>
</div>
</form>
</template>
<script setup>
const supabase = useSupabaseClient()
const loading = ref(true)
const username = ref('')
const website = ref('')
const avatar_path = ref('')
loading.value = true
const user = useSupabaseUser();
let { data } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', user.value.id)
.single()
if (data) {
username.value = data.username
website.value = data.website
avatar_path.value = data.avatar_url
}
loading.value = false
async function updateProfile() {
try {
loading.value = true
const user = useSupabaseUser();
const updates = {
id: user.value.id,
username: username.value,
website: website.value,
avatar_url: avatar_path.value,
updated_at: new Date(),
}
let { error } = await supabase.from('profiles').upsert(updates, {
returning: 'minimal', // Don't return the value after inserting
})
if (error) throw error
} catch (error) {
alert(error.message)
} finally {
loading.value = false
}
}
async function signOut() {
try {
loading.value = true
let { error } = await supabase.auth.signOut()
if (error) throw error
user.value = null
} catch (error) {
alert(error.message)
} finally {
loading.value = false
}
}
</script>
```
### Launch!
Now that we have all the components in place, let's update `app.vue`:
```html title=app.vue
<template>
<div class="container" style="padding: 50px 0 100px 0">
<Account v-if="user" />
<Auth v-else />
</div>
</template>
<script setup>
const user = useSupabaseUser()
</script>
```
Once that's done, run this in a terminal window:
```bash
npm run dev
```
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
![Supabase Nuxt 3](/docs/img/supabase-vue-3-demo.png)
## Bonus: Profile photos
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos.
### Create an upload widget
Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component:
```html title=components/Avatar.vue
<template>
<div>
<img
v-if="src"
:src="src"
alt="Avatar"
class="avatar image"
style="width: 10em; height: 10em;"
/>
<div v-else class="avatar no-image" :style="{ height: size, width: size }" />
<div style="width: 10em; position: relative;">
<label class="button primary block" for="single">
{{ uploading ? "Uploading ..." : "Upload" }}
</label>
<input
style="position: absolute; visibility: hidden;"
type="file"
id="single"
accept="image/*"
@change="uploadAvatar"
:disabled="uploading"
/>
</div>
</div>
</template>
<script setup>
const props = defineProps(['path'])
const { path } = toRefs(props)
const emit = defineEmits(['update:path', 'upload'])
const supabase = useSupabaseClient()
const uploading = ref(false)
const src = ref('')
const files = ref()
const downloadImage = async () => {
try {
const { data, error } = await supabase.storage.from('avatars').download(path.value)
if (error) throw error
src.value = URL.createObjectURL(data)
} catch (error) {
console.error('Error downloading image: ', error.message)
}
}
const uploadAvatar = async (evt) => {
files.value = evt.target.files
try {
uploading.value = true
if (!files.value || files.value.length === 0) {
throw new Error('You must select an image to upload.')
}
const file = files.value[0]
const fileExt = file.name.split('.').pop()
const fileName = `${Math.random()}.${fileExt}`
const filePath = `${fileName}`
let { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file)
if (uploadError) throw uploadError
emit('update:path', filePath)
emit('upload')
} catch (error) {
alert(error.message)
} finally {
uploading.value = false
}
}
downloadImage()
watch(path, () => {
if (path.value) {
downloadImage()
}
})
</script>
```
### Add the new widget
And then we can add the widget to the Account page:
```html title=components/Account.vue
<template>
<form class="form-widget" @submit.prevent="updateProfile">
<Avatar v-model:path="avatar_path" @upload="updateProfile" />
<div>
<label for="email">Email</label>
<input id="email" type="text" :value="user.email" disabled />
</div>
<div>
<label for="username">Name</label>
<input id="username" type="text" v-model="username" />
</div>
<div>
<label for="website">Website</label>
<input id="website" type="website" v-model="website" />
</div>
<div>
<input
type="submit"
class="button primary block"
:value="loading ? 'Loading ...' : 'Update'"
:disabled="loading"
/>
</div>
<div>
<button class="button block" @click="signOut" :disabled="loading">Sign Out</button>
</div>
</form>
</template>
<script setup>
const supabase = useSupabaseClient()
const loading = ref(true)
const username = ref('')
const website = ref('')
const avatar_path = ref('')
loading.value = true
const user = useSupabaseUser();
let { data } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', user.value.id)
.single()
if (data) {
username.value = data.username
website.value = data.website
avatar_path.value = data.avatar_url
}
loading.value = false
async function updateProfile() {
try {
loading.value = true
const user = useSupabaseUser();
const updates = {
id: user.value.id,
username: username.value,
website: website.value,
avatar_url: avatar_path.value,
updated_at: new Date(),
}
let { error } = await supabase.from('profiles').upsert(updates, {
returning: 'minimal', // Don't return the value after inserting
})
if (error) throw error
} catch (error) {
alert(error.message)
} finally {
loading.value = false
}
}
async function signOut() {
try {
loading.value = true
let { error } = await supabase.auth.signOut()
if (error) throw error
} catch (error) {
alert(error.message)
} finally {
loading.value = false
}
}
</script>
```
That is it! You should now be able to upload a profile photo to Supabase Storage.
## Next steps
At this stage you have a fully functional application!
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
- Sign in: [app.supabase.com](https://app.supabase.com)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/with-react.mdx'
export const meta = {
id: 'with-react',
@@ -8,11 +7,397 @@ export const meta = {
sidebar_label: 'React',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
<QuickstartIntro />
![Supabase User Management example](/docs/img/user-management-demo.png)
### GitHub
Should you get stuck while working through the guide, refer to [this repo](https://github.com/supabase/supabase/tree/master/examples/user-management/react-user-management).
<ProjectSetup />
## Building the App
Let's start building the React app from scratch.
### Initialize a React app
We can use [Create React App](https://create-react-app.dev/docs/getting-started/) to initialize
an app called `supabase-react`:
```bash
npx create-react-app supabase-react
cd supabase-react
```
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js).
```bash
npm install @supabase/supabase-js
```
And finally we want to save the environment variables in a `.env`.
All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys).
```bash title=.env
REACT_APP_SUPABASE_URL=YOUR_SUPABASE_URL
REACT_APP_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
```
Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client. These variables will be exposed
on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
```js title=src/supabaseClient.js
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = process.env.REACT_APP_SUPABASE_URL
const supabaseAnonKey = process.env.REACT_APP_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
```
And one optional step is to update the CSS file `src/index.css` to make the app look nice.
You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/react-user-management/src/index.css).
### Set up a Login component
Let's set up a React component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.
```jsx title=src/Auth.js
import { useState } from 'react'
import { supabase } from './supabaseClient'
export default function Auth() {
const [loading, setLoading] = useState(false)
const [email, setEmail] = useState('')
const handleLogin = async (e) => {
e.preventDefault()
try {
setLoading(true)
const { error } = await supabase.auth.signInWithOtp({ email })
if (error) throw error
alert('Check your email for the login link!')
} catch (error) {
alert(error.error_description || error.message)
} finally {
setLoading(false)
}
}
return <Layout meta={meta} children={children} />
return (
<div className="row flex-center flex">
<div className="col-6 form-widget" aria-live="polite">
<h1 className="header">Supabase + React</h1>
<p className="description">Sign in via magic link with your email below</p>
{loading ? (
'Sending magic link...'
) : (
<form onSubmit={handleLogin}>
<label htmlFor="email">Email</label>
<input
id="email"
className="inputField"
type="email"
placeholder="Your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button className="button block" aria-live="polite">
Send magic link
</button>
</form>
)}
</div>
</div>
)
}
```
### Account page
After a user is signed in we can allow them to edit their profile details and manage their account.
Let's create a new component for that called `Account.js`.
```jsx title=src/Account.js
import { useState, useEffect } from 'react'
import { supabase } from './supabaseClient'
const Account = ({ session }) => {
const [loading, setLoading] = useState(true)
const [username, setUsername] = useState(null)
const [website, setWebsite] = useState(null)
const [avatar_url, setAvatarUrl] = useState(null)
useEffect(() => {
getProfile()
}, [session])
const getProfile = async () => {
try {
setLoading(true)
const { user } = session
let { data, error, status } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', user.id)
.single()
if (error && status !== 406) {
throw error
}
if (data) {
setUsername(data.username)
setWebsite(data.website)
setAvatarUrl(data.avatar_url)
}
} catch (error) {
alert(error.message)
} finally {
setLoading(false)
}
}
const updateProfile = async (e) => {
e.preventDefault()
try {
setLoading(true)
const { user } = session
const updates = {
id: user.id,
username,
website,
avatar_url,
updated_at: new Date(),
}
let { error } = await supabase.from('profiles').upsert(updates)
if (error) {
throw error
}
} catch (error) {
alert(error.message)
} finally {
setLoading(false)
}
}
return (
<div aria-live="polite">
{loading ? (
'Saving ...'
) : (
<form onSubmit={updateProfile} className="form-widget">
<div>Email: {session.user.email}</div>
<div>
<label htmlFor="username">Name</label>
<input
id="username"
type="text"
value={username || ''}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div>
<label htmlFor="website">Website</label>
<input
id="website"
type="url"
value={website || ''}
onChange={(e) => setWebsite(e.target.value)}
/>
</div>
<div>
<button className="button primary block" disabled={loading}>
Update profile
</button>
</div>
</form>
)}
<button type="button" className="button block" onClick={() => supabase.auth.signOut()}>
Sign Out
</button>
</div>
)
}
export default Account
```
### Launch!
Now that we have all the components in place, let's update `App.js`:
```jsx title=src/App.js
import './index.css'
import { useState, useEffect } from 'react'
import { supabase } from './supabaseClient'
import Auth from './Auth'
import Account from './Account'
export default function App() {
const [session, setSession] = useState(null)
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session)
})
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session)
})
}, [])
return (
<div className="container" style={{ padding: '50px 0 100px 0' }}>
{!session ? <Auth /> : <Account key={session.user.id} session={session} />}
</div>
)
}
```
Once that's done, run this in a terminal window:
```bash
npm start
```
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
![Supabase React](/docs/img/supabase-react-demo.png)
## Bonus: Profile photos
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos.
### Create an upload widget
Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component:
```jsx title=src/Avatar.js
import { useEffect, useState } from 'react'
import { supabase } from './supabaseClient'
export default function Avatar({ url, size, onUpload }) {
const [avatarUrl, setAvatarUrl] = useState(null)
const [uploading, setUploading] = useState(false)
useEffect(() => {
if (url) downloadImage(url)
}, [url])
const downloadImage = async (path) => {
try {
const { data, error } = await supabase.storage.from('avatars').download(path)
if (error) {
throw error
}
const url = URL.createObjectURL(data)
setAvatarUrl(url)
} catch (error) {
console.log('Error downloading image: ', error.message)
}
}
const uploadAvatar = async (event) => {
try {
setUploading(true)
if (!event.target.files || event.target.files.length === 0) {
throw new Error('You must select an image to upload.')
}
const file = event.target.files[0]
const fileExt = file.name.split('.').pop()
const fileName = `${Math.random()}.${fileExt}`
const filePath = `${fileName}`
let { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file)
if (uploadError) {
throw uploadError
}
onUpload(filePath)
} catch (error) {
alert(error.message)
} finally {
setUploading(false)
}
}
return (
<div style={{ width: size }} aria-live="polite">
<img
src={avatarUrl ? avatarUrl : `https://place-hold.it/${size}x${size}`}
alt={avatarUrl ? 'Avatar' : 'No image'}
className="avatar image"
style={{ height: size, width: size }}
/>
{uploading ? (
'Uploading...'
) : (
<>
<label className="button primary block" htmlFor="single">
Upload an avatar
</label>
<div className="visually-hidden">
<input
type="file"
id="single"
accept="image/*"
onChange={uploadAvatar}
disabled={uploading}
/>
</div>
</>
)}
</div>
)
}
```
### Add the new widget
And then we can add the widget to the Account page:
```jsx title=src/Account.js
// Import the new component
import Avatar from './Avatar'
// ...
return (
<div className="form-widget">
{/* Add to the body */}
<Avatar
url={avatar_url}
size={150}
onUpload={(url) => {
setAvatarUrl(url)
updateProfile({ username, website, avatar_url: url })
}}
/>
{/* ... */}
</div>
)
```
## Next steps
At this stage you have a fully functional application!
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
- Sign in: [app.supabase.com](https://app.supabase.com)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/with-redwoodjs.mdx'
export const meta = {
id: 'with-redwoodjs',
@@ -8,11 +7,779 @@ export const meta = {
sidebar_label: 'RedwoodJS',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
## Intro
For the sake of consistency with the other framework Quickstart examples, we'll build a RedwoodJS a little differently than normal.
We **_won't use_** Prisma to connect to the Supabase Postgres database or [Prisma migrations](https://redwoodjs.com/docs/cli-commands#prisma-migrate) as one typically might in a Redwood app.
Instead, we'll rely on the Supabase client to do some of the work on the **web** side and use the client again on the **API** side to do data fetching as well.
That means you will want to refrain from running any `yarn rw prisma migrate` commands and also double check your build commands on deployment to ensure Prisma won't reset your database.
<Admonition type="note">
TLDR; Prisma currently doesn't support cross-schema foreign keys, so introspecting the schema fails due to how your Supabase `public` schema references the `auth.users`.
</Admonition>
This example provides the steps to build a simple user management app (from scratch!) using Supabase and [RedwoodJS](https://redwoodjs.com/docs/introduction). 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.
- Instant [APIs](/docs/guides/api): APIs will be automatically generated when you create your database tables.
- [Row Level Security](/docs/guides/auth#row-level-security): data is protected so that individuals can only access their own data.
By the end of this guide you'll have an app which allows users to login and update some basic profile details:
![Supabase User Management example](/docs/img/user-management-demo.png)
<Admonition type="note">
Note: For RedwoodJS apps, port will be 8910
</Admonition>
### GitHub
If you get stuck at any point, take a look at [this repo](https://github.com/redwoodjs/redwoodjs-supabase-quickstart).
{/* ## Video demo */}
{/* Coming soon. */}
{/* Use the annotated timeline to step through this tutorial. */}
{/* ## Instant deploy */}
{/* Want to deploy a completed version of this example without following the guide? */}
{/* [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fsupabase%2Fsupabase%2Ftree%2Fmaster%2Fexamples%2Freact-user-management&project-name=supabase-user-management&repository-name=supabase-user-management&demo-title=Supabase%20User%20Management&demo-description=An%20example%20web%20app%20using%20Supabase%20and%20Next.js&demo-url=https%3A%2F%2Fsupabase-react-user-management.vercel.app&demo-image=https%3A%2F%2Fi.imgur.com%2FZ3HkQqe.png&integration-ids=oac_jUduyjQgOyzev1fjrW83NYOv&external-id=nextjs-user-management) */}
{/* Clicking this button the application will: */}
{/* - Launch and prepare the Postgres database in Supabase. */}
{/* - Launch the app in Vercel. */}
{/* - Fork the example into your own GitHub account. */}
{/* - Prepare the deployed application with all the necessary environment variables. */}
{/* If you want to do it yourself, let's get started:` */}
## About RedwoodJS
A Redwood application is split into two parts: a frontend and a backend. This is represented as two node projects within a single monorepo.
The frontend project is called **`web`** and the backend project is called **`api`**. For clarity, we will refer to these in prose as **"sides"**, i.e. the "web side" and the "api side".
They are separate projects because code on the `web side` will end up running in the user's browser while code on the `api side` will run on a server somewhere.
<Admonition type="note">
Important: When this guide refers to "API", that means the Supabase API and when it refers to "api side", that means the RedwoodJS `api side`.
</Admonition>
The **`api side`** is an implementation of a GraphQL API. The business logic is organized into "services" that represent their own internal API and can be called both from external GraphQL requests and other internal services.
The **`web side`** is built with React. Redwood's router makes it simple to map URL paths to React "Page" components (and automatically code-split your app on each route).
Pages may contain a "Layout" component to wrap content. They also contain "Cells" and regular React components.
Cells allow you to declaratively manage the lifecycle of a component that fetches and displays data.
<Admonition type="note">
For the sake of consistency with the other framework Quickstart examples, we'll build a RedwoodJS a little differently than normal.
We **_won't use_** Prisma to connect to the Supabase Postgres database or [Prisma migrations](https://redwoodjs.com/docs/cli-commands#prisma-migrate) as one typically might in a Redwood app.
Instead, we'll rely on the Supabase client to do some of the work on the **`web`** side and use the client again on the **`api`** side to do data fetching as well.
</Admonition>
## 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.
<Admonition type="note">
When setting up Supabase, you'll interact with your project dashboard. However, most of the RedwoodJS setup steps will interact with the [Redwood CLI](https://redwoodjs.com/docs/cli-commands) to generate routes, pages, components, and more.
So be sure to have a terminal at the ready and in your project directory.
</Admonition>
### 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">
```sql
-- Create a table for public "profiles"
create table profiles (
id uuid references auth.users not null,
updated_at timestamp with time zone,
username text unique,
avatar_url text,
website text,
primary key (id),
unique(username),
constraint username_length check (char_length(username) >= 3)
);
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 );
-- Set up Realtime!
begin;
drop publication if exists supabase_realtime;
create publication supabase_realtime;
commit;
alter publication supabase_realtime add table profiles;
-- Set up Storage!
insert into storage.buckets (id, name)
values ('avatars', 'avatars');
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' );
```
</TabPanel>
</Tabs>
#### Important Note about Prisma and Migrations
<Admonition type="note">
Because this Quick Start does not use Prisma to manage the database schema, seed it, or run migrations, you **need** to take extra care when running some of the typical Redwood CLI commands.
</Admonition>
### 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 as well as the `anon`, `service_role` and `JWT_SECRET` keys from the API settings.
1. Go to the [API Settings](https://app.supabase.com/project/_/settings/api) page in the Dashboard.
2. 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>
## Building the App
Let's start building the RedwoodJS app from scratch.
<Admonition type="note">
RedwoodJS requires Node.js `>= 14.x <= 16.x` and Yarn `>= 1.15`.
</Admonition>
Make sure you have installed yarn since RedwoodJS relies on it to [manage its packages in workspaces](https://classic.yarnpkg.com/lang/en/docs/workspaces/) for its `web` and `api` "sides".
### Initialize a RedwoodJS app
We can use [Create Redwood App](https://redwoodjs.com/docs/quick-start) command to initialize
an app called `supabase-redwoodjs`:
```bash
yarn create redwood-app supabase-redwoodjs
cd supabase-redwoodjs
```
While the app is installing, you should see:
```bash
✔ Creating Redwood app
✔ Checking node and yarn compatibility
✔ Creating directory 'supabase-redwoodjs'
✔ Installing packages
✔ Running 'yarn install'... (This could take a while)
✔ Convert TypeScript files to JavaScript
✔ Generating types
Thanks for trying out Redwood!
```
Then let's install the only additional dependency [supabase-js](https://github.com/supabase/supabase-js) by running the `setup auth` command:
```bash
yarn redwood setup auth supabase
```
When prompted:
> Overwrite existing /api/src/lib/auth.[jt]s?
Say, **yes** and it will setup the Supabase client in your app and also provide hooks used with Supabase authentication.
```bash
✔ Generating auth lib...
✔ Successfully wrote file `./api/src/lib/auth.js`
✔ Adding auth config to web...
✔ Adding auth config to GraphQL API...
✔ Adding required web packages...
✔ Installing packages...
✔ One more thing...
You will need to add your Supabase URL (SUPABASE_URL), public API KEY,
and JWT SECRET (SUPABASE_KEY, and SUPABASE_JWT_SECRET) to your .env file.
```
Next, we want to save the environment variables in a `.env`.
We need the `API URL` as well as the `anon` and `jwt_secret` keys that you copied [earlier](#get-the-api-keys).
```bash title=.env
SUPABASE_URL=YOUR_SUPABASE_URL
SUPABASE_KEY=YOUR_SUPABASE_ANON_KEY
SUPABASE_JWT_SECRET=YOUR_SUPABASE_JWT_SECRET
```
And finally, you will also need to save **just** the `web side` environment variables to the `redwood.toml`.
```bash title=redwood.toml
[web]
title = "Supabase Redwood Quickstart"
port = 8910
apiProxyPath = "/.redwood/functions"
includeEnvironmentVariables = ["SUPABASE_URL", "SUPABASE_KEY"]
[api]
port = 8911
[browser]
open = true
```
These variables will be exposed on the browser, and that's completely fine.
They allow your web app to initialize the Supabase client with your public anon key
since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
You'll see these being used to configure your Supabase client in `web/src/App.js`:
```js title=web/src/App.js
// ... Redwood imports
import { AuthProvider } from '@redwoodjs/auth'
import { createClient } from '@supabase/supabase-js'
// ...
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY)
const App = () => (
<FatalErrorBoundary page={FatalErrorPage}>
<RedwoodProvider titleTemplate="%PageTitle | %AppTitle">
<AuthProvider client={supabase} type="supabase">
<RedwoodApolloProvider>
<Routes />
</RedwoodApolloProvider>
</AuthProvider>
</RedwoodProvider>
</FatalErrorBoundary>
)
export default App
```
And one optional step is to update the CSS file `web/src/index.css` to make the app look nice.
You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/react-user-management/src/index.css).
### Start RedwoodJS and your first Page
Let's test our setup at the moment by starting up the app:
```bash
yarn rw dev
```
<Admonition type="note">
Note: You can also use the alias `rw` for `redwood`, as in `yarn rw` to run Redwood CLI commands
</Admonition>
You should see a "Welcome to RedwoodJS" page and a message about not having any pages yet.
So, let's create a "home" page:
```bash
yarn rw generate page home /
✔ Generating page files...
✔ Successfully wrote file `./web/src/pages/HomePage/HomePage.stories.js`
✔ Successfully wrote file `./web/src/pages/HomePage/HomePage.test.js`
✔ Successfully wrote file `./web/src/pages/HomePage/HomePage.js`
✔ Updating routes file...
✔ Generating types ...
```
<Admonition type="note">
Note: the slash `/` is important here as it creates a root level route.
</Admonition>
You can stop the `dev` server if you want; to see your changes, just be sure to run `yarn rw dev` again.
You should see the `Home` page route in `web/src/Routes.js`:
```bash title=web/src/Routes.js
import { Router, Route } from '@redwoodjs/router'
const Routes = () => {
return (
<Router>
<Route path="/" page={HomePage} name="home" />
<Route notfound page={NotFoundPage} />
</Router>
)
}
export default Routes
```
### Set up a Login component
Let's set up a Redwood component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.
```bash
yarn rw g component auth
✔ Generating component files...
✔ Successfully wrote file `./web/src/components/Auth/Auth.test.js`
✔ Successfully wrote file `./web/src/components/Auth/Auth.stories.js`
✔ Successfully wrote file `./web/src/components/Auth/Auth.js`
```
Now, update the `Auth.js` component to contain:
```jsx title=/web/src/components/Auth/Auth.js
import { useState } from 'react'
import { useAuth } from '@redwoodjs/auth'
const Auth = () => {
const { logIn } = useAuth()
const [loading, setLoading] = useState(false)
const [email, setEmail] = useState('')
const handleLogin = async (email) => {
try {
setLoading(true)
const { error } = await logIn({ email })
if (error) throw error
alert('Check your email for the login link!')
} catch (error) {
alert(error.error_description || error.message)
} finally {
setLoading(false)
}
}
return (
<div className="row flex-center flex">
<div className="col-6 form-widget">
<h1 className="header">Supabase + RedwoodJS</h1>
<p className="description">Sign in via magic link with your email below</p>
<div>
<input
className="inputField"
type="email"
placeholder="Your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div>
<button
onClick={(e) => {
e.preventDefault()
handleLogin(email)
}}
className={'button block'}
disabled={loading}
>
{loading ? <span>Loading</span> : <span>Send magic link</span>}
</button>
</div>
</div>
</div>
)
}
export default Auth
```
### Set up an Account component
After a user is signed in we can allow them to edit their profile details and manage their account.
Let's create a new component for that called `Account.js`.
```bash
yarn rw g component account
✔ Generating component files...
✔ Successfully wrote file `./web/src/components/Account/Account.test.js`
✔ Successfully wrote file `./web/src/components/Account/Account.stories.js`
✔ Successfully wrote file `./web/src/components/Account/Account.js`
```
And then update the file to contain:
```jsx title=web/src/components/Account/Account.js
import { useState, useEffect } from 'react'
import { useAuth } from '@redwoodjs/auth'
const Account = () => {
const { client: supabase, currentUser, logOut } = useAuth()
const [loading, setLoading] = useState(true)
const [username, setUsername] = useState(null)
const [website, setWebsite] = useState(null)
const [avatar_url, setAvatarUrl] = useState(null)
useEffect(() => {
getProfile()
}, [supabase.auth.session])
async function getProfile() {
try {
setLoading(true)
const user = supabase.auth.user()
let { data, error, status } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', user.id)
.single()
if (error && status !== 406) {
throw error
}
if (data) {
setUsername(data.username)
setWebsite(data.website)
setAvatarUrl(data.avatar_url)
}
} catch (error) {
alert(error.message)
} finally {
setLoading(false)
}
}
async function updateProfile({ username, website, avatar_url }) {
try {
setLoading(true)
const user = supabase.auth.user()
const updates = {
id: user.id,
username,
website,
avatar_url,
updated_at: new Date(),
}
let { error } = await supabase.from('profiles').upsert(updates, {
returning: 'minimal', // Don't return the value after inserting
})
if (error) {
throw error
}
alert('Updated profile!')
} catch (error) {
alert(error.message)
} finally {
setLoading(false)
}
}
return (
<div className="row flex-center flex">
<div className="col-6 form-widget">
<h1 className="header">Supabase + RedwoodJS</h1>
<p className="description">Your profile</p>
<div className="form-widget">
<div>
<label htmlFor="email">Email</label>
<input id="email" type="text" value={currentUser.email} disabled />
</div>
<div>
<label htmlFor="username">Name</label>
<input
id="username"
type="text"
value={username || ''}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div>
<label htmlFor="website">Website</label>
<input
id="website"
type="website"
value={website || ''}
onChange={(e) => setWebsite(e.target.value)}
/>
</div>
<div>
<button
className="button primary block"
onClick={() => updateProfile({ username, website, avatar_url })}
disabled={loading}
>
{loading ? 'Loading ...' : 'Update'}
</button>
</div>
<div>
<button className="button block" onClick={() => logOut()}>
Sign Out
</button>
</div>
</div>
</div>
</div>
)
}
export default Account
```
<Admonition type="note">
Note: You'll see the use of `useAuth()` several times in the quickstart. Redwood's `useAuth` hook provides convenient ways to access
logIn, logOut, currentUser, and access the `supabase` authenticate client. We'll use it to get an instance
of the supabase client to interact with your API.
</Admonition>
### Update Home Page
Now that we have all the components in place, let's update your `HomePage` page to use them:
```jsx title=web/src/pages/HomePage/HomePage.js
import { useAuth } from '@redwoodjs/auth'
import { MetaTags } from '@redwoodjs/web'
import Account from 'src/components/Account'
import Auth from 'src/components/Auth'
const HomePage = () => {
const { isAuthenticated } = useAuth()
return (
<>
<MetaTags title="Welcome" />
{!isAuthenticated ? <Auth /> : <Account />}
</>
)
}
export default HomePage
```
> What we're doing here is showing the sign in form if you aren't logged in and your account profile if you are.
### Launch!
Once that's done, run this in a terminal window to launch the `dev` server:
```bash
yarn rw dev
```
And then open the browser to [localhost:8910](http://localhost:8910) and you should see the completed app.
![Supabase RedwoodJS](/docs/img/supabase-redwoodjs-demo.png)
## Bonus: Profile photos
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos.
### Create an upload widget
Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component:
```bash
yarn rw g component avatar
✔ Generating component files...
✔ Successfully wrote file `./web/src/components/Avatar/Avatar.test.js`
✔ Successfully wrote file `./web/src/components/Avatar/Avatar.stories.js`
✔ Successfully wrote file `./web/src/components/Avatar/Avatar.js`
```
Now, update your Avatar component to contain the following widget:
```jsx title=web/src/components/Avatar/Avatar.js
import { useEffect, useState } from 'react'
import { useAuth } from '@redwoodjs/auth'
const Avatar = ({ url, size, onUpload }) => {
const { client: supabase } = useAuth()
const [avatarUrl, setAvatarUrl] = useState(null)
const [uploading, setUploading] = useState(false)
useEffect(() => {
if (url) downloadImage(url)
}, [url])
async function downloadImage(path) {
try {
const { data, error } = await supabase.storage.from('avatars').download(path)
if (error) {
throw error
}
const url = URL.createObjectURL(data)
setAvatarUrl(url)
} catch (error) {
console.log('Error downloading image: ', error.message)
}
}
async function uploadAvatar(event) {
try {
setUploading(true)
if (!event.target.files || event.target.files.length === 0) {
throw new Error('You must select an image to upload.')
}
const file = event.target.files[0]
const fileExt = file.name.split('.').pop()
const fileName = `${Math.random()}.${fileExt}`
const filePath = `${fileName}`
let { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file)
if (uploadError) {
throw uploadError
}
onUpload(filePath)
} catch (error) {
alert(error.message)
} finally {
setUploading(false)
}
}
return (
<div>
{avatarUrl ? (
<img
src={avatarUrl}
alt="Avatar"
className="avatar image"
style={{ height: size, width: size }}
/>
) : (
<div className="avatar no-image" style={{ height: size, width: size }} />
)}
<div style={{ width: size }}>
<label className="button primary block" htmlFor="single">
{uploading ? 'Uploading ...' : 'Upload'}
</label>
<input
style={{
visibility: 'hidden',
position: 'absolute',
}}
type="file"
id="single"
accept="image/*"
onChange={uploadAvatar}
disabled={uploading}
/>
</div>
</div>
)
}
export default Avatar
```
### Add the new widget
And then we can add the widget to the Account component:
```jsx title=web/src/components/Account/Account.js
// Import the new component
import Avatar from 'src/components/Avatar'
// ...
return (
<div className="form-widget">
{/* Add to the body */}
<Avatar
url={avatar_url}
size={150}
onUpload={(url) => {
setAvatarUrl(url)
updateProfile({ username, website, avatar_url: url })
}}
/>
{/* ... */}
</div>
)
```
## Next steps
At this stage you have a fully functional application!
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
- Sign in: [app.supabase.com](https://app.supabase.com)
- Learn more about [RedwoodJS](https://redwoodjs.com)
- Visit the [RedwoodJS Discourse Community](https://community.redwoodjs.com)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/with-solidjs.mdx'
export const meta = {
id: 'with-solidjs',
@@ -8,11 +7,421 @@ export const meta = {
sidebar_label: 'SolidJS',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
<QuickstartIntro />
![Supabase User Management example](/docs/img/user-management-demo.png)
### GitHub
Should you get stuck while working through the guide, refer to [this repo](https://github.com/supabase/supabase/tree/master/examples/user-management/solid-user-management).
<ProjectSetup />
## Building the App
Let's start building the SolidJS app from scratch.
### Initialize a SolidJS app
We can use [Degit](https://github.com/Rich-Harris/degit) to initialize an app called `supabase-solid`:
```bash
npx degit solidjs/templates/ts supabase-solid
cd supabase-solid
```
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)
```bash
npm install @supabase/supabase-js
```
And finally we want to save the environment variables in a `.env`.
All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys).
```bash title=.env
VITE_SUPABASE_URL=YOUR_SUPABASE_URL
VITE_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
```
Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client. These variables will be exposed
on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
```js title=src/supabaseClient.jsx
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
```
And one optional step is to update the CSS file `src/index.css` to make the app look nice.
You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/solid-user-management/src/index.css).
### Set up a Login component
Let's set up a SolidJS component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.
```jsx title=src/Auth.tsx
import { createSignal } from 'solid-js'
import { supabase } from './supabaseClient'
export default function Auth() {
const [loading, setLoading] = createSignal(false)
const [email, setEmail] = createSignal('')
const handleLogin = async (e: SubmitEvent) => {
e.preventDefault()
try {
setLoading(true)
const { error } = await supabase.auth.signInWithOtp({ email: email() })
if (error) throw error
alert('Check your email for the login link!')
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
setLoading(false)
}
}
return <Layout meta={meta} children={children} />
return (
<div class="row flex-center flex">
<div class="col-6 form-widget" aria-live="polite">
<h1 class="header">Supabase + SolidJS</h1>
<p class="description">Sign in via magic link with your email below</p>
<form class="form-widget" onSubmit={handleLogin}>
<div>
<label for="email">Email</label>
<input
id="email"
class="inputField"
type="email"
placeholder="Your email"
value={email()}
onChange={(e) => setEmail(e.currentTarget.value)}
/>
</div>
<div>
<button type="submit" class="button block" aria-live="polite">
{loading() ? <span>Loading</span> : <span>Send magic link</span>}
</button>
</div>
</form>
</div>
</div>
)
}
```
### Account page
After a user is signed in we can allow them to edit their profile details and manage their account.
Let's create a new component for that called `Account.tsx`.
```jsx title=src/Account.tsx
import { AuthSession } from '@supabase/supabase-js'
import { Component, createEffect, createSignal } from 'solid-js'
import { supabase } from './supabaseClient'
interface Props {
session: AuthSession;
}
const Account: Component<Props> = ({ session }) => {
const [loading, setLoading] = createSignal(true)
const [username, setUsername] = (createSignal < string) | (null > null)
const [website, setWebsite] = (createSignal < string) | (null > null)
const [avatarUrl, setAvatarUrl] = (createSignal < string) | (null > null)
createEffect(() => {
getProfile()
})
const getProfile = async () => {
try {
setLoading(true)
const { user } = session
let { data, error, status } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', user.id)
.single()
if (error && status !== 406) {
throw error
}
if (data) {
setUsername(data.username)
setWebsite(data.website)
setAvatarUrl(data.avatar_url)
}
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
setLoading(false)
}
}
const updateProfile = async (e: Event) => {
e.preventDefault()
try {
setLoading(true)
const { user } = session
const updates = {
id: user.id,
username: username(),
website: website(),
avatar_url: avatarUrl(),
updated_at: new Date().toISOString(),
}
let { error } = await supabase.from('profiles').upsert(updates)
if (error) {
throw error
}
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
setLoading(false)
}
}
return (
<div aria-live="polite">
<form onSubmit={updateProfile} class="form-widget">
<div>Email: {session.user.email}</div>
<div>
<label for="username">Name</label>
<input
id="username"
type="text"
value={username() || ''}
onChange={(e) => setUsername(e.currentTarget.value)}
/>
</div>
<div>
<label for="website">Website</label>
<input
id="website"
type="text"
value={website() || ''}
onChange={(e) => setWebsite(e.currentTarget.value)}
/>
</div>
<div>
<button type="submit" class="button primary block" disabled={loading()}>
{loading() ? 'Saving ...' : 'Update profile'}
</button>
</div>
<button type="button" class="button block" onClick={() => supabase.auth.signOut()}>
Sign Out
</button>
</form>
</div>
)
}
export default Account
```
### Launch!
Now that we have all the components in place, let's update `App.tsx`:
```jsx title=src/App.tsx
import { Component, createEffect, createSignal } from 'solid-js'
import { supabase } from './supabaseClient'
import { AuthSession } from '@supabase/supabase-js'
import Account from './Account'
import Auth from './Auth'
const App: Component = () => {
const [session, setSession] = createSignal<AuthSession | null>(null)
createEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session)
})
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session)
})
})
return (
<div class="container" style={{ padding: '50px 0 100px 0' }}>
{!session() ? <Auth /> : <Account session={session()!} />}
</div>
)
}
export default App
```
Once that's done, run this in a terminal window:
```bash
npm start
```
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
![Supabase SolidJS](/docs/img/supabase-solidjs-demo.png)
## Bonus: Profile photos
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos.
### Create an upload widget
Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component:
```jsx title=src/Avatar.tsx
import { Component, createEffect, createSignal, JSX } from 'solid-js'
import { supabase } from './supabaseClient'
interface Props {
size: number
url: string | null
onUpload: (event: Event, filePath: string) => void
}
const Avatar: Component<Props> = (props) => {
const [avatarUrl, setAvatarUrl] = createSignal<string | null>(null)
const [uploading, setUploading] = createSignal(false)
createEffect(() => {
if (props.url) downloadImage(props.url)
})
const downloadImage = async (path: string) => {
try {
const { data, error } = await supabase.storage.from('avatars').download(path)
if (error) {
throw error
}
const url = URL.createObjectURL(data)
setAvatarUrl(url)
} catch (error) {
if (error instanceof Error) {
console.log('Error downloading image: ', error.message)
}
}
}
const uploadAvatar: JSX.EventHandler<HTMLInputElement, Event> = async (event) => {
try {
setUploading(true)
const target = event.currentTarget
if (!target?.files || target.files.length === 0) {
throw new Error('You must select an image to upload.')
}
const file = target.files[0]
const fileExt = file.name.split('.').pop()
const fileName = `${Math.random()}.${fileExt}`
const filePath = `${fileName}`
let { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file)
if (uploadError) {
throw uploadError
}
props.onUpload(event, filePath)
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
setUploading(false)
}
}
return (
<div style={{ width: props.size }} aria-live="polite">
{avatarUrl() ? (
<img
src={avatarUrl()!}
alt={avatarUrl() ? 'Avatar' : 'No image'}
class="avatar image"
style={{ height: `${props.size}px`, width: `${props.size}px` }}
/>
) : (
<div
class="avatar no-image"
style={{ height: `${props.size}px`, width: `${props.size}px` }}
/>
)}
<div style={{ width: `${props.size}px` }}>
<label class="button primary block" for="single">
{uploading() ? 'Uploading ...' : 'Upload avatar'}
</label>
<span style="display:none">
<input
type="file"
id="single"
accept="image/*"
onChange={uploadAvatar}
disabled={uploading()}
/>
</span>
</div>
</div>
)
}
export default Avatar
```
### Add the new widget
And then we can add the widget to the Account page:
```jsx title=src/Account.tsx
// Import the new component
import Avatar from './Avatar'
// ...
return (
<form onSubmit={updateProfile} class="form-widget">
{/* Add to the body */}
<Avatar
url={avatarUrl()}
size={150}
onUpload={(e: Event, url: string) => {
setAvatarUrl(url)
updateProfile(e)
}}
/>
{/* ... */}
</div>
)
```
## Next steps
At this stage you have a fully functional application!
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
- Sign in: [app.supabase.com](https://app.supabase.com)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/with-svelte.mdx'
export const meta = {
id: 'with-svelte',
@@ -8,11 +7,374 @@ export const meta = {
sidebar_label: 'Svelte',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
<QuickstartIntro />
![Supabase User Management example](/docs/img/user-management-demo.png)
### GitHub
Should you get stuck while working through the guide, refer to [this repo](https://github.com/supabase/supabase/tree/master/examples/user-management/svelte-user-management).
<ProjectSetup />
## Building the App
Let's start building the Svelte app from scratch.
### Initialize a Svelte app
We can use the Vite Svelte TypeScript Template to initialize an app called `supabase-svelte`:
```bash
npm create vite@latest supabase-svelte -- --template svelte-ts
cd supabase-svelte
npm install
```
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)
```bash
npm install @supabase/supabase-js
```
And finally we want to save the environment variables in a `.env`.
All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys).
```bash title=.env
VITE_SUPABASE_URL=YOUR_SUPABASE_URL
VITE_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
```
Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client. These variables will be exposed
on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
```js title=src/supabaseClient.ts
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
```
And one optional step is to update the CSS file `src/app.css` to make the app look nice.
You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/svelte-user-management/src/app.css).
### Set up a Login component
Let's set up a Svelte component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.
```html title=src/lib/Auth.svelte
<script lang="ts">
import { supabase } from 'src/supabaseClient'
let loading = false
let email = ''
const handleLogin = async () => {
try {
loading = true
const { error } = await supabase.auth.signInWithOtp({ email })
if (error) throw error
alert('Check your email for login link!')
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
loading = false
}
}
return <Layout meta={meta} children={children} />
}
</script>
<div class="row flex-center flex">
<div class="col-6 form-widget" aria-live="polite">
<h1 class="header">Supabase + Svelte</h1>
<p class="description">Sign in via magic link with your email below</p>
<form class="form-widget" on:submit|preventDefault="{handleLogin}">
<div>
<label for="email">Email</label>
<input
id="email"
class="inputField"
type="email"
placeholder="Your email"
bind:value="{email}"
/>
</div>
<div>
<button type="submit" class="button block" aria-live="polite" disabled="{loading}">
<span>{loading ? 'Loading' : 'Send magic link'}</span>
</button>
</div>
</form>
</div>
</div>
```
### Account page
After a user is signed in we can allow them to edit their profile details and manage their account.
Let's create a new component for that called `Account.svelte`.
```html title=src/lib/Account.svelte
<script lang="ts">
import { onMount } from "svelte";
import type { AuthSession } from "@supabase/supabase-js";
import { supabase } from "../supabaseClient";
export let session: AuthSession;
let loading = false
let username: string | null = null
let website: string | null = null
let avatarUrl: string | null = null
onMount(() => {
getProfile()
})
const getProfile = async () => {
try {
loading = true
const { user } = session
const { data, error, status } = await supabase
.from('profiles')
.select('username, website, avatar_url')
.eq('id', user.id)
.single()
if (error && status !== 406) throw error
if (data) {
username = data.username
website = data.website
avatarUrl = data.avatar_url
}
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
loading = false
}
}
const updateProfile = async () => {
try {
loading = true
const { user } = session
const updates = {
id: user.id,
username,
website,
avatar_url: avatarUrl,
updated_at: new Date().toISOString(),
}
let { error } = await supabase.from('profiles').upsert(updates)
if (error) {
throw error
}
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
loading = false
}
}
</script>
<form on:submit|preventDefault={updateProfile} class="form-widget">
<div>Email: {session.user.email}</div>
<div>
<label for="username">Name</label>
<input id="username" type="text" bind:value={username} />
</div>
<div>
<label for="website">Website</label>
<input id="website" type="text" bind:value={website} />
</div>
<div>
<button type="submit" class="button primary block" disabled={loading}>
{loading ? 'Saving ...' : 'Update profile'}
</button>
</div>
<button type="button" class="button block" on:click={() => supabase.auth.signOut()}>
Sign Out
</button>
</form>
```
### Launch!
Now that we have all the components in place, let's update `App.svelte`:
```html title=src/App.svelte
<script lang="ts">
import { onMount } from 'svelte'
import { supabase } from './supabaseClient'
import type { AuthSession } from '@supabase/supabase-js'
import Account from './lib/Account.svelte'
import Auth from './lib/Auth.svelte'
let session: AuthSession
onMount(() => {
supabase.auth.getSession().then(({ data }) => {
session = data.session
})
supabase.auth.onAuthStateChange((_event, _session) => {
session = _session
})
})
</script>
<div class="container" style="padding: 50px 0 100px 0">
{#if !session}
<Auth />
{:else}
<Account {session} />
{/if}
</div>
```
Once that's done, run this in a terminal window:
```bash
npm run dev
```
And then open the browser to [localhost:5173](http://localhost:5173) and you should see the completed app.
> ⚠️ WARNING: Svelte uses Vite and the default port is `5173`, Supabase uses `port 3000`. To change the redirection port for supabase go to: `Authentication > Settings` and change the `Site Url` to `localhost:5173`
![Supabase Svelte](/docs/img/supabase-svelte-demo.png)
## Bonus: Profile photos
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos.
### Create an upload widget
Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component:
```html title=src/lib/Avatar.svelte
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { supabase } from '../supabaseClient'
export let size: number
export let url: string
let avatarUrl: string = null
let uploading = false
let files: FileList
const dispatch = createEventDispatcher()
const downloadImage = async (path: string) => {
try {
const { data, error } = await supabase.storage.from('avatars').download(path)
if (error) {
throw error
}
const url = URL.createObjectURL(data)
avatarUrl = url
} catch (error) {
if (error instanceof Error) {
console.log('Error downloading image: ', error.message)
}
}
}
const uploadAvatar = async () => {
try {
uploading = true
if (!files || files.length === 0) {
throw new Error('You must select an image to upload.')
}
const file = files[0]
const fileExt = file.name.split('.').pop()
const filePath = `${Math.random()}.${fileExt}`
let { error } = await supabase.storage.from('avatars').upload(filePath, file)
if (error) {
throw error
}
url = filePath
dispatch('upload')
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
uploading = false
}
}
$: if (url) downloadImage(url)
</script>
<div style="width: {size}px" aria-live="polite">
{#if avatarUrl} <img src={avatarUrl} alt={avatarUrl ? 'Avatar' : 'No image'} class="avatar image"
style="height: {size}px, width: {size}px" /> {:else}
<div class="avatar no-image" style="height: {size}px, width: {size}px" />
{/if}
<div style="width: {size}px">
<label class="button primary block" for="single">
{uploading ? 'Uploading ...' : 'Upload avatar'}
</label>
<span style="display:none">
<input
type="file"
id="single"
accept="image/*"
bind:files
on:change="{uploadAvatar}"
disabled="{uploading}"
/>
</span>
</div>
</div>
```
### Add the new widget
And then we can add the widget to the Account page:
```html title=src/lib/Account.svelte
<script lang="ts">
// Import the new component
import Avatar from './Avatar.svelte'
</script>
<form on:submit|preventDefault="{updateProfile}" class="form-widget">
<!-- Add to body -->
<Avatar bind:url="{avatarUrl}" size="{150}" on:upload="{updateProfile}" />
<!-- Other form elements -->
</form>
```
## Next steps
At this stage you have a fully functional application!
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
- Sign in: [app.supabase.com](https://app.supabase.com)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/with-sveltekit.mdx'
export const meta = {
id: 'with-sveltekit',
@@ -8,11 +7,440 @@ export const meta = {
sidebar_label: 'SvelteKit',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
<QuickstartIntro />
![Supabase User Management example](/docs/img/user-management-demo.png)
### GitHub
Should you get stuck while working through the guide, refer to [this repo](https://github.com/supabase/supabase/tree/master/examples/user-management/sveltekit-user-management).
<ProjectSetup />
## Building the App
Let's start building the Svelte app from scratch.
### Initialize a Svelte app
We can use the [SvelteKit Skeleton Project](https://kit.svelte.dev/docs) to initialize
an app called `supabase-sveltekit` (for this tutorial you do not need TypeScript, ESLint, Prettier, or Playwright):
```bash
npm init svelte@next supabase-sveltekit
cd supabase-sveltekit
npm install
```
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)
```bash
npm install @supabase/supabase-js
```
And finally we want to save the environment variables in a `.env`.
All we need are the `SUPABASE_URL` and the `SUPABASE_KEY` key that you copied [earlier](#get-the-api-keys).
```bash title=.env
PUBLIC_SUPABASE_URL="YOUR_SUPABASE_URL"
PUBLIC_SUPABASE_ANON_KEY="YOUR_SUPABASE_KEY"
```
Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client. These variables will be exposed
on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
```js title=src/lib/supabaseClient.ts
import { createClient } from '@supabase/auth-helpers-sveltekit'
import { env } from '$env/dynamic/public'
export const supabase = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY)
```
And one optional step is to update the CSS file `public/global.css` to make the app look nice.
You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/svelte-user-management/src/app.css).
### Supabase Auth Helpers
SvelteKit is a highly versatile framework offering pre-rendering at build time (SSG), server-side rendering at request time (SSR), API routes, and more.
It can be challenging to authenticate your users in all these different environments, that's why we've created the Supabase Auth Helpers to make user management and data fetching within SvelteKit as easy as possible.
Install the auth helpers for SvelteKit:
```bash
npm install @supabase/auth-helpers-sveltekit
```
Update your `src/routes/+layout.svelte`:
```html title=src/routes/+layout.svelte
<script lang="ts">
import { supabase } from '$lib/supabaseClient'
import { invalidate } from '$app/navigation'
import { onMount } from 'svelte'
import './styles.css'
onMount(() => {
const {
data: { subscription },
} = supabase.auth.onAuthStateChange(() => {
invalidate('supabase:auth')
})
return () => {
subscription.unsubscribe()
}
})
</script>
<div class="container" style="padding: 50px 0 100px 0">
<slot />
</div>
```
Create a new `src/routes/+layout.ts` file to handle the session on the client-side.
```ts title=src/routes/+layout.ts
import type { LayoutLoad } from './$types'
import { getSupabase } from '@supabase/auth-helpers-sveltekit'
export const load: LayoutLoad = async (event) => {
const { session } = await getSupabase(event)
return { session }
}
```
Create a new `src/routes/+layout.server.ts` file to handle the session on the server-side.
```ts title=src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types'
import { getServerSession } from '@supabase/auth-helpers-sveltekit'
export const load: LayoutServerLoad = async (event) => {
return {
session: await getServerSession(event),
}
}
```
Be sure to create `src/hooks.client.ts` and `src/hooks.server.ts` in order to get the auth helper started on the client and server-side.
```ts title=src/hooks.client.ts
import '$lib/supabaseClient'
```
```ts title=src/hooks.server.ts
import '$lib/supabaseClient'
```
### Set up a Login component
Let's set up a Svelte component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.
```html title=src/routes/Auth.svelte
<script lang="ts">
import { supabase } from '$lib/supabaseClient'
let loading = false
let email: string
const handleLogin = async () => {
try {
loading = true
const { error } = await supabase.auth.signInWithOtp({ email })
if (error) throw error
alert('Check your email for the login link!')
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
loading = false
}
}
</script>
<form class="row flex-center flex" on:submit|preventDefault="{handleLogin}">
<div class="col-6 form-widget">
<h1 class="header">Supabase + SvelteKit</h1>
<p class="description">Sign in via magic link with your email below</p>
<div>
<input class="inputField" type="email" placeholder="Your email" bind:value="{email}" />
</div>
<div>
<input type="submit" class="button block" value={loading ? 'Loading' : 'Send magic link'}
disabled={loading} />
</div>
</div>
</form>
```
### Account component
After a user is signed in, they need to be able to edit their profile details and manage their account.
Create a new `Account.svelte` component to handle this functionality.
```html title=src/routes/Account.svelte
<script lang="ts">
import { onMount } from 'svelte'
import type { AuthSession } from '@supabase/supabase-js'
import { supabase } from '$lib/supabaseClient'
export let session: AuthSession
let loading = false
let username: string | null = null
let website: string | null = null
let avatarUrl: string | null = null
onMount(() => {
getProfile()
})
const getProfile = async () => {
try {
loading = true
const { user } = session
const { data, error, status } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', user.id)
.single()
if (data) {
username = data.username
website = data.website
avatarUrl = data.avatar_url
}
if (error && status !== 406) throw error
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
loading = false
}
}
async function updateProfile() {
try {
loading = true
const { user } = session
const updates = {
id: user.id,
username,
website,
avatar_url: avatarUrl,
updated_at: new Date(),
}
let { error } = await supabase.from('profiles').upsert(updates)
if (error) throw error
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
loading = false
}
}
async function signOut() {
try {
loading = true
let { error } = await supabase.auth.signOut()
if (error) throw error
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
loading = false
}
}
</script>
<form class="form-widget" on:submit|preventDefault="{updateProfile}">
<div>
<label for="email">Email</label>
<input id="email" type="text" value="{session.user.email}" disabled />
</div>
<div>
<label for="username">Name</label>
<input id="username" type="text" bind:value="{username}" />
</div>
<div>
<label for="website">Website</label>
<input id="website" type="website" bind:value="{website}" />
</div>
<div>
<input type="submit" class="button block primary" value={loading ? 'Loading...' : 'Update'}
disabled={loading} />
</div>
<div>
<button class="button block" on:click="{signOut}" disabled="{loading}">Sign Out</button>
</div>
</form>
```
### Launch!
Now that we have all the components in place, let's update `src/routes/+page.svelte`:
```html title=src/routes/+page.svelte
<script>
import { page } from '$app/stores'
import Account from './Account.svelte'
import Auth from './Auth.svelte'
</script>
<svelte:head>
<title>Supabase + SvelteKit</title>
<meta name="description" content="SvelteKit using supabase-js v2" />
</svelte:head>
{#if !$page.data.session}
<Auth />
{:else}
<Account session="{$page.data.session}" />
{/if}
```
Once that's done, run this in a terminal window:
```bash
npm run dev
```
And then open the browser to [localhost:5173](http://localhost:5173) and you should see the completed app.
![Supabase Svelte](/docs/img/supabase-svelte-demo.png)
## Bonus: Profile photos
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos.
### Create an upload widget
Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component:
```html title=src/routes/Avatar.svelte
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { supabase } from '$lib/supabaseClient'
export let size = 10
export let url: string
let avatarUrl: string | null = null
let uploading = false
let files: FileList
const dispatch = createEventDispatcher()
const downloadImage = async (path: string) => {
try {
const { data, error } = await supabase.storage.from('avatars').download(path)
if (error) {
throw error
}
const url = URL.createObjectURL(data)
avatarUrl = url
} catch (error) {
if (error instanceof Error) {
console.log('Error downloading image: ', error.message)
}
}
}
const uploadAvatar = async () => {
try {
uploading = true
if (!files || files.length === 0) {
throw new Error('You must select an image to upload.')
}
const file = files[0]
const fileExt = file.name.split('.').pop()
const filePath = `${Math.random()}.${fileExt}`
let { error } = await supabase.storage.from('avatars').upload(filePath, file)
if (error) {
throw error
}
url = filePath
dispatch('upload')
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
uploading = false
}
}
$: if (url) downloadImage(url)
</script>
<div>
{#if avatarUrl} <img src={avatarUrl} alt={avatarUrl ? 'Avatar' : 'No image'} class="avatar image"
style="height: {size}em; width: {size}em;" /> {:else}
<div class="avatar no-image" style="height: {size}em; width: {size}em;" />
{/if}
<div style="width: {size}em;">
<label class="button primary block" for="single">
{uploading ? 'Uploading ...' : 'Upload'}
</label>
<input
style="visibility: hidden; position:absolute;"
type="file"
id="single"
accept="image/*"
bind:files
on:change="{uploadAvatar}"
disabled="{uploading}"
/>
</div>
</div>
```
### Add the new widget
And then we can add the widget to the Account page:
```html title=src/routes/Account.svelte
<script>
// Import the new component
import Avatar from './Avatar.svelte'
</script>
<form use:getProfile class="form-widget" on:submit|preventDefault="{updateProfile}">
<!-- Add to body -->
<Avatar bind:url="{avatarUrl}" size="{10}" on:upload="{updateProfile}" />
<!-- Other form elements -->
</form>
```
## Next steps
At this stage you have a fully functional application!
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
- Sign in: [app.supabase.com](https://app.supabase.com)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,5 +1,4 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/with-vue-3.mdx'
export const meta = {
id: 'with-vue-3',
@@ -8,11 +7,385 @@ export const meta = {
sidebar_label: 'Vue 3',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
<QuickstartIntro />
![Supabase User Management example](/docs/img/user-management-demo.png)
### GitHub
Should you get stuck while working through the guide, refer to [this repo](https://github.com/supabase/supabase/tree/master/examples/user-management/vue3-user-management).
<ProjectSetup />
## Building the App
Let's start building the Vue 3 app from scratch.
### Initialize a Vue 3 app
We can quickly use [Vite with Vue 3 Template](https://vitejs.dev/guide/#scaffolding-your-first-vite-project) to initialize
an app called `supabase-vue-3`:
```bash
# npm 6.x
npm create vite@latest supabase-vue-3 --template vue
# npm 7+, extra double-dash is needed:
npm create vite@latest supabase-vue-3 -- --template vue
cd supabase-vue-3
```
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)
```bash
npm install @supabase/supabase-js
```
And finally we want to save the environment variables in a `.env`.
All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys).
```bash title=.env
VITE_SUPABASE_URL=YOUR_SUPABASE_URL
VITE_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
```
Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client. These variables will be exposed
on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
```js title=src/supabase.js
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
```
Optionally, update [src/style.css](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/vue3-user-management/src/style.css) to style the app.
### Set up a Login component
Let's set up a Vue component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.
```html title=/src/components/Auth.vue
<script setup>
import { ref } from 'vue'
import { supabase } from '../supabase'
const loading = ref(false)
const email = ref('')
const handleLogin = async () => {
try {
loading.value = true
const { error } = await supabase.auth.signInWithOtp({
email: email.value,
})
if (error) throw error
alert('Check your email for the login link!')
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
loading.value = false
}
}
return <Layout meta={meta} children={children} />
}
</script>
<template>
<form class="row flex-center flex" @submit.prevent="handleLogin">
<div class="col-6 form-widget">
<h1 class="header">Supabase + Vue 3</h1>
<p class="description">Sign in via magic link with your email below</p>
<div>
<input class="inputField" type="email" placeholder="Your email" v-model="email" />
</div>
<div>
<input
type="submit"
class="button block"
:value="loading ? 'Loading' : 'Send magic link'"
:disabled="loading"
/>
</div>
</div>
</form>
</template>
```
### Account page
After a user is signed in we can allow them to edit their profile details and manage their account.
Let's create a new component for that called `Account.vue`.
```html title=src/components/Account.vue
<script setup>
import { supabase } from '../supabase'
import { onMounted, ref, toRefs } from 'vue'
const props = defineProps(['session'])
const { session } = toRefs(props)
const loading = ref(true)
const username = ref('')
const website = ref('')
const avatar_url = ref('')
onMounted(() => {
getProfile()
})
async function getProfile() {
try {
loading.value = true
const { user } = session.value
let { data, error, status } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', user.id)
.single()
if (error && status !== 406) throw error
if (data) {
username.value = data.username
website.value = data.website
avatar_url.value = data.avatar_url
}
} catch (error) {
alert(error.message)
} finally {
loading.value = false
}
}
async function updateProfile() {
try {
loading.value = true
const { user } = session.value
const updates = {
id: user.id,
username: username.value,
website: website.value,
avatar_url: avatar_url.value,
updated_at: new Date(),
}
let { error } = await supabase.from('profiles').upsert(updates)
if (error) throw error
} catch (error) {
alert(error.message)
} finally {
loading.value = false
}
}
async function signOut() {
try {
loading.value = true
let { error } = await supabase.auth.signOut()
if (error) throw error
} catch (error) {
alert(error.message)
} finally {
loading.value = false
}
}
</script>
<template>
<form class="form-widget" @submit.prevent="updateProfile">
<div>
<label for="email">Email</label>
<input id="email" type="text" :value="session.user.email" disabled />
</div>
<div>
<label for="username">Name</label>
<input id="username" type="text" v-model="username" />
</div>
<div>
<label for="website">Website</label>
<input id="website" type="website" v-model="website" />
</div>
<div>
<input
type="submit"
class="button primary block"
:value="loading ? 'Loading ...' : 'Update'"
:disabled="loading"
/>
</div>
<div>
<button class="button block" @click="signOut" :disabled="loading">Sign Out</button>
</div>
</form>
</template>
```
### Launch!
Now that we have all the components in place, let's update `App.vue`:
```html title=src/App.vue
<script setup>
import { onMounted, ref } from 'vue'
import Account from './components/Account.vue'
import Auth from './components/Auth.vue'
import { supabase } from './supabase'
const session = ref()
onMounted(() => {
supabase.auth.getSession().then(({ data }) => {
session.value = data.session
})
supabase.auth.onAuthStateChange((_, _session) => {
session.value = _session
})
})
</script>
<template>
<div class="container" style="padding: 50px 0 100px 0">
<Account v-if="session" :session="session" />
<Auth v-else />
</div>
</template>
```
Once that's done, run this in a terminal window:
```bash
npm run dev
```
And then open the browser to [localhost:5173](http://localhost:5173) and you should see the completed app.
![Supabase Vue 3](/docs/img/supabase-vue-3-demo.png)
## Bonus: Profile photos
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos.
### Create an upload widget
Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component:
```html title=src/components/Avatar.vue
<script setup>
import { ref, toRefs, watch } from 'vue'
import { supabase } from '../supabase'
const prop = defineProps(['path', 'size'])
const { path, size } = toRefs(prop)
const emit = defineEmits(['upload', 'update:path'])
const uploading = ref(false)
const src = ref('')
const files = ref()
const downloadImage = async () => {
try {
const { data, error } = await supabase.storage.from('avatars').download(path.value)
if (error) throw error
src.value = URL.createObjectURL(data)
} catch (error) {
console.error('Error downloading image: ', error.message)
}
}
const uploadAvatar = async (evt) => {
files.value = evt.target.files
try {
uploading.value = true
if (!files.value || files.value.length === 0) {
throw new Error('You must select an image to upload.')
}
const file = files.value[0]
const fileExt = file.name.split('.').pop()
const filePath = `${Math.random()}.${fileExt}`
let { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file)
if (uploadError) throw uploadError
emit('update:path', filePath)
emit('upload')
} catch (error) {
alert(error.message)
} finally {
uploading.value = false
}
}
watch(path, () => {
if (path.value) downloadImage()
})
</script>
<template>
<div>
<img
v-if="src"
:src="src"
alt="Avatar"
class="avatar image"
:style="{ height: size + 'em', width: size + 'em' }"
/>
<div v-else class="avatar no-image" :style="{ height: size + 'em', width: size + 'em' }" />
<div :style="{ width: size + 'em' }">
<label class="button primary block" for="single">
{{ uploading ? "Uploading ..." : "Upload" }}
</label>
<input
style="visibility: hidden; position: absolute"
type="file"
id="single"
accept="image/*"
@change="uploadAvatar"
:disabled="uploading"
/>
</div>
</div>
</template>
```
### Add the new widget
And then we can add the widget to the Account page:
```html title=src/components/Account.vue
<script>
// Import the new component
import Avatar from './Avatar.vue'
</script>
<template>
<form class="form-widget" @submit.prevent="updateProfile">
<!-- Add to body -->
<Avatar v-model:path="avatar_url" @upload="updateProfile" size="10" />
<!-- Other form elements -->
</form>
</template>
```
## Next steps
At this stage you have a fully functional application!
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
- Sign in: [app.supabase.com](https://app.supabase.com)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
-120
View File
@@ -1,120 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'docker',
title: 'Self-hosting with Docker',
description: 'How to configure and deploy Supabase.',
sidebar_label: 'Docker',
}
Docker is the easiest way to get started with self-hosted Supabase.
## Before you begin
You need the following installed in your system:
- [Docker](https://docs.docker.com/engine/install/) and [docker-compose](https://docs.docker.com/compose/install/)
- [Git](https://git-scm.com/downloads)
## Quick Start
### Get the code
Checkout the docker directory in the Supabase repo:
```sh
# Get the code
git clone --depth 1 https://github.com/supabase/supabase
# Go to the docker folder
cd supabase/docker
# Copy the fake env vars
cp .env.example .env
# Start
docker-compose up
```
Now visit [http://localhost:3000](http://localhost:3000) to start using Supabase Studio.
## Securing your setup
While we provided you with some example secrets for getting started, you should NEVER deploy your Supabase setup using the defaults we have provided.
Please follow these steps to secure your Docker setup. We [strongly recommend](../../guides/hosting/overview#managing-your-secrets) using a secrets manager when deploying to production.
### Generate API Keys
Use your `JWT_SECRET` to generate a `anon` and `service` API keys using the [JWT generator](../../guides/hosting/overview#api-keys).
Replace the values in these files:
- `.env`:
- `ANON_KEY` - replace with an `anon` key
- `SERVICE_ROLE_KEY` - replace with a `service` key
- `volumes/api/kong.yml`
- `anon` - replace with an `anon` key
- `service_role` - replace with a `service` key
### Update Secrets
Update the `.env` file with your own secrets. In particular, these are required:
- `POSTGRES_PASSWORD`: the password for the `postgres` role.
- `JWT_SECRET`: used by PostgREST and GoTrue, among others.
- `SITE_URL`: the base URL of your site.
- `SMTP_*`: mail server credentials. You can use any SMTP server.
### Securing the Dashboard
The Docker setup doesn't include a management database for managing users and logins. If you plan to deploy the Studio to the web we suggest you put it behind a web proxy with Basic Auth or hide it behind a VPN.
## Configuration
Each system can be [configured](../../guides/hosting/overview#configuration) to suit your particular use-case.
To keep the setup simple, we made some choices that may not be optimal for production:
- the database is in the same machine as the servers
- Storage uses the filesystem backend instead of S3
- Auth should be configured with a production-ready SMTP server
### Using an external database
We strongly [recommend](../../guides/hosting/overview#managing-your-database) that you decouple your database from `docker-compose` before deploying.
The middleware will run with any PostgreSQL database that has logical replication enabled. The following environment variables should be updated
in the `.env` file to point to your external database:
```env title=.env
POSTGRES_PASSWORD=your-super-secret-and-long-postgres-password
POSTGRES_HOST=db
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PORT=5432
```
Once you have done this, you can safely comment out the `db` section of the `docker-compose` file, and remove any instances where the services `depends_on` the `db` image.
### Setting database's `log_min_messages`
By default, `docker-compose` sets the database's `log_min_messages` configuration to `fatal` to prevent redundant logs generated by Realtime.
However, you might miss important log messages such as database errors. Configure `log_min_messages` based on your needs.
## Deploying
See the following guides to deploy Docker Compose setup using your preferred tool and platform:
- [Docker Swarm](https://docs.docker.com/engine/swarm/stack-deploy/)
- [AWS Fargate](https://aws.amazon.com/blogs/containers/deploy-applications-on-amazon-ecs-using-docker-compose/)
- [Using Kompose for Kubernetes](https://kubernetes.io/docs/tasks/configure-pod-container/translate-compose-kubernetes/)
## Next steps
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
- Sign in: [app.supabase.com](https://app.supabase.com)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
-161
View File
@@ -1,161 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'overview',
title: 'Self Hosting',
sidebar_label: 'Overview',
description: 'Getting started with Self Hosting.',
}
There are several ways to use Supabase:
- [Supabase Cloud](https://app.supabase.com): you don't need to deploy anything. We will manage and scale your infrastructure.
- [Docker](../../guides/hosting/docker): deploy to your own infrastructure.
- Kubernetes: coming soon.
## Architecture
Supabase is a combination of open source tools, each specifically chosen for Enterprise-readiness.
If the tools and communities already exist, with an MIT, Apache 2, or equivalent open license, we will use and support that tool.
If the tool doesn't exist, we build and open source it ourselves.
![Supabase Architecture](/docs/img/supabase-architecture.png)
- [Kong](https://github.com/Kong/kong) is a cloud-native API gateway.
- [GoTrue](https://github.com/netlify/gotrue) is an SWT based API for managing users and issuing SWT tokens.
- [PostgREST](http://postgrest.org/) is a web server that turns your PostgreSQL database directly into a RESTful API
- [Realtime](https://github.com/supabase/realtime) is an Elixir server that allows you to listen to PostgreSQL inserts, updates, and deletes using websockets. Realtime polls Postgres' built-in replication functionality for database changes, converts changes to JSON, then broadcasts the JSON over websockets to authorized clients.
- [Storage](https://github.com/supabase/storage-api) provides a RESTful interface for managing Files stored in S3, using Postgres to manage permissions.
- [postgres-meta](https://github.com/supabase/postgres-meta) is a RESTful API for managing your Postgres, allowing you to fetch tables, add roles, and run queries, etc.
- [PostgreSQL](https://www.postgresql.org/) is an object-relational database system with over 30 years of active development that has earned it a strong reputation for reliability, feature robustness, and performance.
## Configuration
Each system has a number of configuration options which can be found in the relevant product documentation.
- [Postgres](https://hub.docker.com/_/postgres/)
- [PostgREST](https://postgrest.org/en/stable/configuration.html)
- [Realtime](https://github.com/supabase/realtime#server)
- [GoTrue](https://github.com/supabase/gotrue)
- [Storage](https://github.com/supabase/storage-api)
- [Kong](https://docs.konghq.com/gateway/latest/install/docker/)
## Managing your database
It is recommended that you decouple your database from the middleware so that you can upgrade the middleware without any downtime.
The "middleware" is everything except Postgres, and it should work with any Postgres provider (such as AWS RDS), or your own Postgres cluster.
### Extensions
Supabase requires some Postgres extensions to be enabled by default for the API and Auth system to work. You can find the extensions inside the
[schema migration scripts](https://github.com/supabase/postgres/tree/develop/migrations). These are mounted at `/docker-entrypoint-initdb.d`
to run automatically when starting the database container.
We recommend installing all extensions into an `extensions` schema. This will keep your API clean,
since all tables in the `public` schema are exposed via the API.
```sql
create schema if not exists extensions;
create extension if not exists "uuid-ossp" with schema extensions;
create extension if not exists pgcrypto with schema extensions;
create extension if not exists pgjwt with schema extensions;
```
##### `uuid-ossp`
For UUID functions, required for PostgreSQL `<13`.
##### `pgcrypto` and `pgjwt`
For working with JWT and Auth functions.
### Roles
Supabase creates several default roles in your Postgres database. To restore defaults at any time you can run the commands inside the
[schema initialization scripts](https://github.com/supabase/postgres/tree/develop/migrations/db/init-scripts). Remember to change your
[role passwords](./docker#securing-your-setup) before deploying to production environments.
##### `postgres`
The default PostgreSQL role. This has admin privileges.
##### `anon`
For "anonymous access". This is the role which the API (PostgREST) will use when a user _is not_ logged in.
##### `authenticator`
A special role for the API (PostgREST). It has very limited access, and is used to validate a JWT and then
"change into" another role determined by the JWT verification.
##### `authenticated`
For "authenticated access". This is the role which the API (PostgREST) will use when a user _is_ logged in.
##### `service_role`
For elevated access. This role is used by the API (PostgREST) to bypass Row Level Security.
##### `supabase_auth_admin`
Used by the Auth middleware to connect to the database and run migration. Access is scoped to the `auth` schema.
##### `supabase_storage_admin`
Used by the Auth middleware to connect to the database and run migration. Access is scoped to the `storage` schema.
##### `dashboard_user`
For running commands via the Supabase UI.
##### `supabase_admin`
Supabase Administrative role for maintaining your database.
### Realtime Logs
Set your database's `log_min_messages` configuration to `fatal` to prevent redundant database logs generated by Realtime. However, you might miss important log messages such as database errors. Configure `log_min_messages` based on your needs.
## API Keys
The API Gateway (Kong) uses JWT to authenticate access through to the database. The JWT should correspond to a relevant Postgres Role,
and Supabase is designed to work with 2 roles: an `ANON_KEY` for unauthenticated access and a `SERVICE_KEY` for elevated access.
Use this tool to generate keys:
<JwtGenerator />
## Managing your secrets
Many components inside Supabase use secure secrets and passwords. These are listed in the self-hosting
[env file](https://github.com/supabase/supabase/blob/master/docker/.env.example), but we strongly recommend using a
secrets manager when deploying to production. Plain text files like dotenv lead to accidental costly leaks.
Some suggested systems include:
- [Doppler](https://www.doppler.com/)
- [Key Vault](https://docs.microsoft.com/en-us/azure/key-vault/general/overview) by Azure
- [Secrets Manager](https://aws.amazon.com/secrets-manager/) by AWS
- [Secrets Manager](https://cloud.google.com/secret-manager) by GCP
- [Vault](https://www.hashicorp.com/products/vault) by Hashicorp
## Migrating and Upgrading
If you have decoupled your database from the middleware, then you should be able to redeploy the latest middleware at any time as long as it has no breaking changes.
Supabase is evolving fast, and we'll continue to improve the migration strategy as part of our core offering.
We realize that database migrations are difficult, and this is one of the problems we plan to make easy for developers.
## Deployment options
While Supabase officially supports Docker, we have several other deployment strategies managed by the community:
- [supabase-docker](../../guides/hosting/docker) (Official)
- [supabase-kubernetes](https://github.com/supabase-community/supabase-kubernetes) (Unofficial)
- [supabase-terraform](https://github.com/supabase-community/supabase-terraform) (Unofficial)
- [supabase-traefik](https://github.com/supabase-community/supabase-traefik) (Unofficial)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,93 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'firebase-auth',
title: 'Firebase Auth Migration',
description: 'Migrate Firebase auth users to a Supabase project.',
sidebar_label: 'Firebase Auth',
}
Supabase provides several [tools](https://github.com/supabase-community/firebase-to-supabase/tree/main/auth) to help migrate auth users from a Firebase project to a Supabase project. There are two parts to the migration process:
- `firestoreusers2json` ([TypeScript](https://github.com/supabase-community/firebase-to-supabase/blob/main/auth/firestoreusers2json.ts), [JavaScript](https://github.com/supabase-community/firebase-to-supabase/blob/main/auth/firestoreusers2json.js)) exports users from an existing Firebase project to a `.json` file on your local system.
- `import_users` ([TypeScript](https://github.com/supabase-community/firebase-to-supabase/blob/main/auth/import_users.ts), [JavaScript](https://github.com/supabase-community/firebase-to-supabase/blob/main/auth/import_users.js)) imports users from a saved `.json` file into your Supabase project (inserting those users into the `auth.users` table of your `PostgreSQL` database instance).
## Set up the migration tool [#set-up-migration-tool]
1. Clone the [firebase-to-supabase](https://github.com/supabase-community/firebase-to-supabase) repository:
```bash
git clone https://github.com/supabase-community/firebase-to-supabase.git
```
1. In the `/auth` directory, create a file named `supabase-service.json` with the following contents:
```json
{
"host": "database.server.com",
"password": "secretpassword",
"user": "postgres",
"database": "postgres",
"port": 5432
}
```
1. Go to the [Database settings](https://app.supabase.com/project/_/settings/database) for your project in the Supabase Dashboard.
1. Under **Connection Info**, copy the Host string and replace the entry in your `supabase-service.json` file.
1. Enter the password you used when you created your Supabase project in the `password` entry in the `supabase-service.json` file.
## Generate a Firebase private key [#generate-firebase-private-key]
1. Log in to your [Firebase Console](https://console.firebase.google.com/project) and open your project.
1. Click the gear icon next to **Project Overview** in the sidebar and select **Project Settings**.
1. Click **Service Accounts** and select **Firebase Admin SDK**.
1. Click **Generate new private key**.
1. Rename the downloaded file to `firebase-service.json`.
## Save your Firebase password hash parameters [#save-firebase-hash-parameters]
1. Log in to your [Firebase Console](https://console.firebase.google.com/project) and open your project.
1. Select **Authentication** (Build section) in the sidebar.
1. Select **Users** in the top menu.
1. At the top right of the users list, open the menu (3 dots) and click **Password hash parameters**.
1. Copy and save the parameters for `base64_signer_key`, `base64_salt_separator`, `rounds`, and `mem_cost`.
```bash title=Sample%20password%20hash%20parameters
hash_config {
algorithm: SCRYPT,
base64_signer_key: XXXX/XXX+XXXXXXXXXXXXXXXXX+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX==,
base64_salt_separator: Aa==,
rounds: 8,
mem_cost: 14,
}
```
## Command line options
### Dump Firestore users to a JSON file [#dump-firestore-users]
`node firestoreusers2json.js [<filename.json>] [<batch_size>]`
- `filename.json`: (optional) output filename (defaults to `./users.json`)
- `batchSize`: (optional) number of users to fetch in each batch (defaults to 100)
### Import JSON users file to Supabase Auth (PostgreSQL: auth.users) [#import-json-users-file]
`node import_users.js <path_to_json_file> [<batch_size>]`
- `path_to_json_file`: full local path and filename of .json input file (of users)
- `batch_size`: (optional) number of users to process in a batch (defaults to 100)
## Notes
For more advanced migrations, including the use of a middleware server component for verifying a user's existing Firebase password and updating that password in your Supabase project the first time a user logs in, see the [firebase-to-supabase repo](https://github.com/supabase-community/firebase-to-supabase/tree/main/auth).
## Resources
- [Supabase vs Firebase](https://supabase.com/alternatives/supabase-vs-firebase)
- [Firestore Data Migration](/docs/guides/migrations/firestore-data)
- [Firestore Storage Migration](/docs/guides/migrations/firebase-storage)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,72 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'firebase-storage',
title: 'Firebase Storage Migration',
description: 'Migrate Firebase Storage files to Supabase Storage.',
sidebar_label: 'Firebase Storage',
}
Supabase provides several [tools](https://github.com/supabase-community/firebase-to-supabase/tree/main/storage) to convert storage files from Firebase Storage to Supabase Storage. Conversion is a two-step process:
1. Files are downloaded from a Firebase storage bucket to a local filesystem.
2. Files are uploaded from the local filesystem to a Supabase storage bucket.
## Set up the migration tool [#set-up-migration-tool]
1. Clone the [firebase-to-supabase](https://github.com/supabase-community/firebase-to-supabase) repository:
```bash
git clone https://github.com/supabase-community/firebase-to-supabase.git
```
1. In the `/storage` directory, rename [supabase-keys-sample.js](https://github.com/supabase-community/firebase-to-supabase/blob/main/storage/supabase-keys-sample.js) to `supabase-keys.js`.
1. Go to your Supabase project's [API settings](https://app.supabase.com/project/_/settings/api) in the Dashboard.
1. Copy the **Project URL** and update the `SUPABASE_URL` value in `supabase-keys.js`.
1. Under **Project API keys**, copy the **service_role** key and update the `SUPABASE_KEY` value in `supabase-keys.js`.
## Generate a Firebase private key [#generate-firebase-private-key]
1. Log in to your [Firebase Console](https://console.firebase.google.com/project) and open your project.
1. Click the gear icon next to **Project Overview** in the sidebar and select **Project Settings**.
1. Click **Service Accounts** and select **Firebase Admin SDK**.
1. Click **Generate new private key**.
1. Rename the downloaded file to `firebase-service.json`.
## Command line options
### Download Firestore Storage bucket to a local filesystem folder [#download-firestore-storage-bucket]
`node download.js <prefix> [<folder>] [<batchSize>] [<limit>] [<token>]`
- `<prefix>`: The prefix of the files to download. To process the root bucket, use an empty prefix: "".
- `<folder>`: (optional) Name of subfolder for downloaded files. The selected folder is created as a subfolder of the current folder (e.g., `./downloads/`). The default is `downloads`.
- `<batchSize>`: (optional) The default is 100.
- `<limit>`: (optional) Stop after processing this many files. For no limit, use `0`.
- `<token>`: (optional) Begin processing at this pageToken.
To process in batches using multiple command-line executions, you must use the same parameters with a new `<token>` on subsequent calls. Use the token displayed on the last call to continue the process at a given point.
### Upload files to Supabase Storage bucket [#upload-to-supabase-storage-bucket]
`node upload.js <prefix> <folder> <bucket>`
- `<prefix>`: The prefix of the files to download. To process all files, use an empty prefix: "".
- `<folder>`: Name of subfolder of files to upload. The selected folder is read as a subfolder of the current folder (e.g., `./downloads/`). The default is `downloads`.
- `<bucket>`: Name of the bucket to upload to.
<Admonition type="note">
If the bucket doesn't exist, it's created as a `non-public` bucket. You must set permissions on this new bucket in the [Supabase Dashboard](https://app.supabase.com/project/_/storage/buckets) before users can download any files.
</Admonition>
## Resources
- [Supabase vs Firebase](https://supabase.com/alternatives/supabase-vs-firebase)
- [Firestore Data Migration](/docs/guides/migrations/firestore-data)
- [Firebase Auth Migration](/docs/guides/migrations/firebase-auth)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,71 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'heroku',
title: 'Migrate from Heroku to Supabase',
description: 'Migrate your Heroku Postgres database to Supabase.',
sidebar_label: 'Heroku',
}
Supabase is one of the best [free alternatives to Heroku Postgres](https://supabase.com/alternatives/supabase-vs-heroku-postgres). This guide shows how to migrate your Heroku Postgres database to Supabase. This migration requires the [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html) and [psql](https://www.postgresql.org/docs/current/app-psql.html) CLI tools, which are installed automatically as part of the complete PostgreSQL installation package.
Alternatively, use the [Heroku to Supabase migration tool](https://migrate.supabase.com/) to migrate in just a few clicks.
## Quick demo
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/xsRhPMphtZ4"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
## Retrieve your Heroku database credentials [#retrieve-heroku-credentials]
1. Log in to your [Heroku account](https://heroku.com) and select the project you want to migrate.
1. Click **Resources** in the menu and select your **Heroku Postgres** database.
1. Click **Settings** in the menu.
1. Click **View Credentials** and save the following information:
- Host (`$HEROKU_HOST`)
- Database (`$HEROKU_DATABASE`)
- User (`$HEROKU_USER`)
- Password (`$HEROKU_PASSWORD`)
## Retrieve your Supabase Host [#retrieve-supabase-host]
1. If you're new to Supabase, [create a project](https://app.supabase.com).
1. Go to the [Database settings](https://app.supabase.com/project/_/settings/database) for your project in the Supabase Dashboard.
1. Under **Connection Info**, note your Host (`$SUPABASE_HOST`).
## Export your Heroku database to a file [#export-heroku-database]
Use `pg_dump` with your Heroku credentials to export your Heroku database to a file (e.g., `heroku_dump.sql`).
```bash
pg_dump --clean --if-exists --quote-all-identifiers \
-h $HEROKU_HOST -U $HEROKU_USER -d $HEROKU_DATABASE \
--no-owner --no-privileges > heroku_dump.sql
```
## Import the database to your Supabase project [#import-database-to-supabase]
Use `psql` to import the Heroku database file to your Supabase project.
```bash
psql -h $SUPABASE_HOST -U postgres -f heroku_dump.sql
```
## Additional options
- To only migrate a single database schema, add the `--schema=PATTERN` parameter to your `pg_dump` command.
- To exclude a schema: `--exclude-schema=PATTERN`.
- To only migrate a single table: `--table=PATTERN`.
- To exclude a table: `--exclude-table=PATTERN`.
Run `pg_dump --help` for a full list of options.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+1 -44
View File
@@ -25,50 +25,7 @@ Each project on Supabase comes with:
## Organizations
Organizations are a way to group your projects. Each organization can be configured with different team members and billing settings.
## Manage team members
You can invite your team members into your organizations to collaborate on projects.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/invite-team.mp4" type="video/mp4" muted playsInline />
</video>
You can also assign roles to your team members with different access levels. The table below shows the corresponding permissions for each available role in the Dashboard.
| Permissions | Owner | Administrator | Developer |
| ------------------------ | ----- | ------------- | --------- |
| **Organization** |
| Change organization name | ✅ | | |
| Delete organization | ✅ | | |
| **Members** |
| Add an Owner | ✅ | | |
| Remove an Owner | ✅ | | |
| Add an Administrator | ✅ | ✅ | |
| Remove an Administrator | ✅ | ✅ | |
| Add a Developer | ✅ | ✅ | |
| Remove a Developer | ✅ | ✅ | |
| Revoke an invite | ✅ | ✅ | |
| Resend an invite | ✅ | ✅ | |
| **Billing** |
| Read invoices | ✅ | ✅ | ✅ |
| Read billing email | ✅ | ✅ | ✅ |
| Change billing email | ✅ | | |
| View subscription | ✅ | ✅ | ✅ |
| Update subscription | ✅ | ✅ | |
| Read billing address | ✅ | ✅ | ✅ |
| Update billing address | ✅ | ✅ | |
| Read tax codes | ✅ | ✅ | ✅ |
| Update tax codes | ✅ | ✅ | |
| Read payment methods | ✅ | ✅ | ✅ |
| Update payment methods | ✅ | ✅ | |
| **Projects** |
| Create a project | ✅ | ✅ | |
| Delete a project | ✅ | ✅ | |
| Update a project | ✅ | ✅ | |
| Pause a project | ✅ | ✅ | |
| Resume a project | ✅ | ✅ | |
| Restart a project | ✅ | ✅ | ✅ |
Refer to [access control](/docs/guides/platform/access-control) for more information on how to manage team members within an organization.
## Platform status
@@ -0,0 +1,85 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
title: 'Access Control',
description: 'Roles and permissions at the organization level',
}
Supabase provides granular access control features that let you manage permissions across your organizations.
Within a Supabase organization, a member can have one of the following roles:
- Owner
- Administrator
- Developer
A default organization is created for a user when they first sign-in and
assigned the **Owner** role. If the user wants to invite others
to collaborate within the organization, they can visit the organization team
settings (`https://app.supabase.com/org/<org-slug>/settings#team`) to send an
invite link to another user's email. The invite expires after 24 hours.
Invites sent from a SSO account can only be accepted by another SSO account
coming from the same identity provider. This is a security measure that
prevents accidental invites to accounts not managed by your company's
enterprise systems.
Project level invites are not available at this time. A member of the
organization will be able to access all projects under the organization. If you
wish to restrict access to certain projects, please create another organization
to manage this.
## Manage team members
You can invite your team members into your organizations to collaborate on projects.
<video width="99%" muted playsInline controls="true">
<source src="/docs/videos/invite-team.mp4" type="video/mp4" muted playsInline />
</video>
### Permissions across roles [#permission-across-roles]
The table below shows the corresponding permissions for each available role you can assign a team member in the Dashboard.
| Permissions | Owner | Administrator | Developer |
| ------------------------ | ----- | ------------- | --------- |
| **Organization** |
| Change organization name | ✅ | | |
| Delete organization | ✅ | | |
| **Members** |
| Add an Owner | ✅ | | |
| Remove an Owner | ✅ | | |
| Add an Administrator | ✅ | ✅ | |
| Remove an Administrator | ✅ | ✅ | |
| Add a Developer | ✅ | ✅ | |
| Remove a Developer | ✅ | ✅ | |
| Revoke an invite | ✅ | ✅ | |
| Resend an invite | ✅ | ✅ | |
| Accept an invite[^1] | ✅ | ✅ | ✅ |
| **Billing** |
| Read invoices | ✅ | ✅ | ✅ |
| Read billing email | ✅ | ✅ | ✅ |
| Change billing email | ✅ | | |
| View subscription | ✅ | ✅ | ✅ |
| Update subscription | ✅ | ✅ | |
| Read billing address | ✅ | ✅ | ✅ |
| Update billing address | ✅ | ✅ | |
| Read tax codes | ✅ | ✅ | ✅ |
| Update tax codes | ✅ | ✅ | |
| Read payment methods | ✅ | ✅ | ✅ |
| Update payment methods | ✅ | ✅ | |
| **Projects** |
| Create a project | ✅ | ✅ | |
| Delete a project | ✅ | ✅ | |
| Update a project | ✅ | ✅ | |
| Pause a project | ✅ | ✅ | |
| Resume a project | ✅ | ✅ | |
| Restart a project | ✅ | ✅ | ✅ |
[^1]:
If the invite was sent from a SSO account, it can only be accepted from
a user signed in via the same identity provider.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,75 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'network-restrictions',
title: 'Network Restrictions',
description: "Apply network restrictions for your project's database.",
}
<Admonition type="note">
Network Restrictions are currently in beta and are being slowly made available to all projects.
The current iteration of Network Restrictions applies to connections to Postgres and PgBouncer. Support for restricting connections to PostgREST, Storage, and Auth will be added at a later date.
</Admonition>
Each Supabase project comes with configurable restrictions on the IP ranges that are allowed to connect to Postgres and PgBouncer ("your database"). These restrictions are enforced before traffic reaches your database. If a connection is not restricted by IP, it still needs to authenticate successfully with valid database credentials.
To get started:
1. [Install](/docs/guides/cli) the Supabase CLI 1.22.0+.
1. [Log in](/docs/guides/cli/local-development#log-in-to-the-supabase-cli) to your Supabase account using the CLI.
1. Ensure that you have [Owner or Admin permissions](/docs/guides/hosting/platform#manage-team-members) for the project that you are enabling network restrictions.
## Check restrictions
You can use the `get` subcommand of the CLI to retrieve the restrictions currently in effect.
If restrictions have been applied, the output of the `get` command will reflect the IP ranges allowed to connect:
```bash
> supabase network-restrictions --project-ref {ref} get --experimental
DB Allowed CIDRs: [128.12.1.1/16 183.12.1.1/24]
Restrictions applied successfully: true
```
If restrictions have never been applied to your project, the list of allowed CIDRs will be empty, but they will also not have been applied ("Restrictions applied successfully: false"). As a result, all IPs are allowed to connect to your database:
```bash
> supabase network-restrictions --project-ref {ref} get --experimental
DB Allowed CIDRs: []
Restrictions applied successfully: false
```
## Update restrictions
The `update` subcommand is used to apply network restrictions to your project:
```bash
> supabase network-restrictions --project-ref {ref} update --db-allow-cidr 128.12.1.1/16 --db-allow-cidr 183.12.1.1/24 --experimental
DB Allowed CIDRs: [128.12.1.1/16 183.12.1.1/24]
Restrictions applied successfully: true
```
The restrictions specified (in the form of CIDRs) replaces any restrictions that might have been applied in the past.
To add to the existing restrictions, you must include the existing restrictions within the list of CIDRs provided to the `update` command.
## Remove restrictions
To remove all restrictions on your project, you can use the `update` subcommand with the CIDR `0.0.0.0/0`:
```bash
> supabase network-restrictions --project-ref {ref} update --db-allow-cidr 0.0.0.0/0 --experimental
DB Allowed CIDRs: [0.0.0.0/0]
Restrictions applied successfully: true
```
## Limitations
1. Network Restrictions do not currently apply to APIs offered over HTTPS (e.g. PostgREST, Storage, Auth).
1. Network Restrictions should not be used if you require you need to connect to your Postgres database using Edge Functions.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+54
View File
@@ -0,0 +1,54 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
title: 'Enable SSO for Your Organization',
description: 'General information about enabling single sign-on (SSO) for your organization',
}
Supabase offers single sign-on (SSO) as a login option to provide additional
account security for your team. This allows company administrators to enforce
the use of an identity provider when logging into Supabase. SSO
improves the onboarding and offboarding experience of the company as the
employee only needs a single set of credentials to access third-party
applications or tools—which can also be revoked easily by an administrator.
<Admonition type="note">
Supabase currently provides SAML SSO. Please contact [Enterprise
Sales](https://supabase.com/contact/enterprise) to have this enabled for your
organization.
</Admonition>
## Understanding setup and implications
Accounts signing in with SSO have certain limitations.
The following sections outline the limitations when SSO is enabled or disabled for your team.
### Enable SSO for your team [#enable-sso]
- Organization invites are restricted to members of the company that belong to
the same identity provider.
- Every user has an organization created by default. They can create as many
projects as they want.
- An SSO user will not be able to update their password or reset their
password since their access is managed by the company administrator via the
identity provider.
- If an SSO user with the following email of `alice@foocorp.com` attempts to
sign-in with a GitHub account that uses the same email, a separate Supabase
account is created and will not be linked to the SSO user's account.
- An SSO user will not be able to see all organizations / projects created
under the same identity provider. They will need to be invited to the
Supabase organization first. Refer to [access control](/docs/guides/platform/access-control)
for more information.
### Disable SSO for your team [#disable-sso]
- You can prevent a user's account from further access to Supabase by removing
or disabling their account in your identity provider.
- You should also remove or downgrade their permissions from any organizations
inside Supabase.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,128 @@
import Layout from '~/layouts/DefaultGuideLayout'
import { Button, IconArrowDown } from 'ui'
export const meta = {
title: 'Set Up SSO with Azure AD',
description: 'Configure single sign-on with Azure AD (Microsoft Entra).',
}
Supabase supports single sign-on (SSO) using Microsoft Azure AD.
## Step 1: Add and register an Enterprise Application [#add-and-register-enterprise-application]
Open up the [Azure Active
Directory](https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/Overview)
dashboard for your Azure account.
Click the _Add_ button then _Enterprise application_.
![Azure AD console: Default Directory Overview](/docs/img/sso-azure-step-01.png)
## Step 2: Choose Create your own application [#create-application]
You'll be using the custom enterprise application setup for Supabase.
![Azure AD console: Browse Azure AD Gallery, select: Create your own
application](/docs/img/sso-azure-step-02.png)
## Step 3: Fill in application details [#add-application-details]
In the modal titled _Create your own application_ enter the name you wish
Supabase to be available to your Azure AD users. `Supabase` works in most
cases.
Make sure to choose the third option: _Integrate any other application you
don't find in the gallery (Non-gallery)_.
![Azure AD console: Create your own application modal](/docs/img/sso-azure-step-03.png)
## Step 4: Choose the Set up single sign-on option [#set-up-single-sign-on]
Before you get to assigning users and groups, which would allow accounts in
Azure AD to access Supabase, you need to configure the SAML details that allows
Supabase to accept sign in requests from Azure AD.
![Azure AD console: Supabase custom enterprise application, selected Set up
single sign-on](/docs/img/sso-azure-step-04.png)
## Step 5: Select SAML single sign-on method [#saml-sso]
Supabase only supports the SAML 2.0 protocol for Single Sign-On, which is an
industry standard.
![Azure AD console: Supabase application, Single sign-on configuration screen,
selected SAML](/docs/img/sso-azure-step-05.png)
## Step 6: Upload SAML-based Sign-on metadata file [#upload-saml-metadata]
First you need to download Supabase's SAML metadata file. Click the button
below to initiate a download of the file.
<a href="https://alt.supabase.io/auth/v1/sso/saml/metadata?download=true">
<Button size="large" icon={<IconArrowDown />}>
Download Supabase SAML Metadata File
</Button>
</a>
Alternatively, visit this page to initiate a download: `https://alt.supabase.io/auth/v1/sso/saml/metadata?download=true`
Click on the _Upload metadata file_ option in the toolbar and select the file
you just downloaded.
![Azure AD console: Supabase application, SAML-based Sign-on screen,
selected Upload metadata file button](/docs/img/sso-azure-step-06-1.png)
All of the correct information should automatically populate the _Basic SAML
Configuration_ screen as shown.
![Azure AD console: Supabase application, SAML-based Sign-on screen,
Basic SAML Configuration shown](/docs/img/sso-azure-step-06-2.png)
**Make sure you input these additional settings.**
| Setting | Value |
| ----------- | -------------------------------------- |
| Sign on URL | `https://app.supabase.com/sign-in-sso` |
| Relay State | `https://app.supabase.com` |
Finally, click the _Save_ button to save the configuration.
## Step 7: Obtain metadata URL and send to Supabase [#send-metadata-url]
Supabase needs to finalize enabling single sign-on with your Azure AD
application. To do this, please copy and send the link under **App Federation
Metadata Url** in \*section 3 **SAML Certificates\*** to your support
contact and await further instructions. If you're not clear who to send this
link to or need further assistance, please reach out to
[support@supabase.com](mailto:support@supabase.com).
**Do not test the login until you have heard back from the support contact.**
![Azure AD console: Supabase application, SAML Certificates card
shown, App Federation Metadata Url highlighted](/docs/img/sso-azure-step-07.png)
## Step 8: Wait for confirmation [#confirmation]
Please wait for confirmation or further instructions from your support contact
at Supabase before proceeding to the next step. It usually takes us 1 business
day to configure SSO for you.
## Step 9: Test single sign-on [#testing]
_Testing sign-on before your Azure AD has been registered with Supabase will
not work. Make sure you've received confirmation from your support contact at
Supabase as laid out in the [confirmation](#confirmation) step._
Once you’ve received confirmation from your support contact at Supabase that
SSO setup has been completed for your enterprise, you can ask some of your
users to sign in via their Azure AD account.
You ask them to enter their email address on the [Sign in with
SSO](https://app.supabase.com/sign-in-sso) page.
If sign in is not working correctly, please reach out to your support contact
at Supabase for further guidance.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,123 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
title: 'Set Up SSO with Google Workspace',
description: 'Configure single sign-on with Google Workspace (GSuite).',
}
Supabase supports single sign-on (SSO) using Google Workspace (formerly known
as GSuite).
## Step 1: Open the Google Workspace Web and mobile apps console [#google-workspace-console]
![Google Workspace: Web and mobile apps admin
console](/docs/img/sso-gsuite-step-01.png)
## Step 2: Choose Add custom SAML app [#add-custom-saml-app]
From the _Add app_ button in the toolbar choose _Add custom SAML app_.
![Google Workspace: Web and mobile apps admin console, Add custom SAML app
selected](/docs/img/sso-gsuite-step-02.png)
## Step 3: Fill out app details [#add-app-details]
The information you enter here is for visibility into your Google Workspace.
You can choose any values you like. `Supabase` as a name works well for most
use cases. Optionally enter a description.
![Google Workspace: Web and mobile apps admin console, Add custom SAML, App
details screen](/docs/img/sso-gsuite-step-03.png)
## Step 4: Download IdP metadata [#download-idp-metadata]
This is a very important step. Click on _DOWNLOAD METADATA_ and save the file
that was downloaded.
![Google Workspace: Web and mobile apps admin console, Add custom SAML, Google
Identity Provider details screen](/docs/img/sso-gsuite-step-04.png)
It's very important to send this file to your support contact at Supabase to
complete the SSO setup process. If you're not sure where to send this file, you
can always reach us at [support@supabase.com](mailto:support@supabase.com).
**Important: Make sure the certificate as shown on screen has at least 1 year
before it expires. Mark down this date in your calendar so you will be reminded
that you need to update the certificate without any downtime for your users.**
## Step 5: Add service provider details [#add-service-provider-details]
Fill out these serivce provider details on the next screen.
| Detail | Value |
| -------------- | --------------------------------------------------- |
| ACS URL | `https://alt.supabase.io/auth/v1/sso/saml/acs` |
| Entity ID | `https://alt.supabase.io/auth/v1/sso/saml/metadata` |
| Start URL | `https://app.supabase.com` |
| Name ID format | PERSISTENT |
| Name ID | _Basic Information > Primary email_ |
![Google Workspace: Web and mobile apps admin console, Add custom SAML,
Service provider details screen](/docs/img/sso-gsuite-step-05.png)
## Step 6: Configure Attribute mapping [#configure-attribute-mapping]
Attribute mappings allow Supabase to get information about your Google
Workspace users on each login.
**A _Primary email_ to `email` mapping is required to exist.** Other mappings
shown below are optional and configurable depending on your Google Workspace
setup. If in doubt, replicate the same config as shown.
Please share any changes, if any, from this screen with your Supabase support
contact.
![Google Workspace: Web and mobile apps admin console, Add custom SAML,
Attribute mapping](/docs/img/sso-gsuite-step-06.png)
## Step 7: Wait for confirmation [#confirmation]
Once you’ve configured the Google Workspace app as shown above, make sure you
send the [metadata file you downloaded](#download-idp-metadata)
and information regarding the [attribute mapping](#configure-attribute-mappings) (if any
changes are applicable) to your support contact at Supabase.
This information needs to be entered into Supabase before SSO is activated
end-to-end.
Wait for confirmation that this information has successfully been added to
Supabase. It usually takes us 1 business day to configure this information
for you.
Supabase.
## Step 8: Configure user access [#configure-user-access]
You can configure which Google Workspace user accounts will get access to
Supabase. This is important if you wish to limit access to your software
engineering teams.
You can configure this access by clicking on the _User
access_ card (or down-arrow). Follow the instructions on screen.
Changes from this step sometimes take a while to propagate across Google’s
systems. Please wait at least 15 minutes before proceeding to the next step.
![Google Workspace: Web and mobile apps admin console, Supabase app
screen](/docs/img/sso-gsuite-step-08.png)
## Step 9: Test single sign-on [#testing]
Once you’ve turned on access to Supabase for users in your organization, ask
one of those users to help you out in testing the setup.
It often helps to ask them to log out of their Google account and log back in.
Ask them to enter their email addres in the [Sign in with
SSO](https://app.supabase.com/sign-in-sso) page.
If sign in is not working correctly, please reach out to your support contact
at Supabase.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -0,0 +1,112 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
title: 'Set Up SSO with Okta',
description: 'Configure single sign-on with Okta.',
}
Supabase supports single sign-on (SSO) using Okta.
## Step 1: Choose Create App Integration in the Applications dashboard [#create-app-integration]
Navigate to the Applications dashboard of the Okta admin console. Choose the
_Create App Integration_ button from the toolbar.
![Okta dashboard: Create App Integration
button](/docs/img/sso-okta-step-01.png)
## Step 2: Choose SAML 2.0 in the app integration dialog [#create-saml-app]
Supabase supports the SAML 2.0 SSO protocol. Choose it from the _Create a new
app integration_ dialog.
![Okta dashboard: Create new app integration dialog](/docs/img/sso-okta-step-02.png)
## Step 3: Fill out General Settings [#add-general-settings]
The information you enter here is for visibility into your Okta applications
menu. You can choose any values you like. `Supabase` as a name works well for
most use cases.
![Okta dashboard: Create SAML Integration
wizard](/docs/img/sso-okta-step-03.png)
## Step 4: Fill out SAML Settings [#add-saml-settings]
These settings let Supabase use SAML 2.0 properly with your Okta application.
Make sure you enter this information exactly as shown on in this table and
screenshot.
| Setting | Value |
| ---------------------------------------------- | ---------------------------------------------------- |
| Single sign-on URL | `https://app.supabase.com/auth/v1/sso/saml/acs` |
| Use this for Recipient URL and Destination URL | ✔️ |
| Audience URI (SP Entity ID) | `https://app.supabase.com/auth/v1/sso/saml/metadata` |
| Default RelayState | `https://app.supabase.com` |
| Name ID format | EmailAddress |
| Application username | Email |
| Update application username on | Create and update |
![Okta dashboard: Create SAML Integration
wizard, Configure SAML step](/docs/img/sso-okta-step-04.png)
## Step 5: Fill out Attribute Statements [#add-attribute-statements]
Attribute Statements allow Supabase to get information about your Okta users on each login.
**A `email` to `user.email` statement is required to exist.** Other mappings
shown below are optional and configurable depending on your Okta
setup. If in doubt, replicate the same config as shown.
Please share any changes, if any, from this screen with your Supabase support
contact.
![Okta dashboard: Attribute Statements configuration
screen](/docs/img/sso-okta-step-05.png)
## Step 6: Obtain IdP metadata URL [#idp-metadata-url]
Supabase needs to finalize enabling single sign-on with your Okta
application.
To do this scroll down to the _SAML Signing Certificates_ section on the _Sign
On_ tab of the _Supabase_ application. Pick the the _SHA-2_ row with an
_Active_ status. Click on the _Actions_ dropdown button and then on the _View
IdP Metadata_.
This will open up the SAML 2.0 Metadata XML file in a new tab in your browser.
Copy this URL and send it to your support contact and await further
instructions. If you're not clear who to send this link to or need further
assistance, please reach out to
[support@supabase.com](mailto:support@supabase.com).
The link usually has this structure: `https://<okta-org>.okta.com/apps/<app-id>/sso/saml/metadata`
![Okta dashboard: SAML Signing Certificates, Actions button highlighted](/docs/img/sso-okta-step-06.png)
## Step 7: Wait for confirmation [#confirmation]
Once you’ve configured the Okta app as shown above, make sure you send the
[metadata URL](#idp-metadata-url) and information regarding the
[attribute statements](#add-attribute-statements) (if any changes are applicable) to
your support contact at Supabase.
Wait for confirmation that this information has successfully been added to
Supabase. It usually takes us 1 business day to configure this information
for you.
## Step 8: Test single sign-on [#testing]
Once you’ve received confirmation from your support contact at Supabase that
SSO setup has been completed for your enterprise, you can ask some of your
users to sign in via their Okta account.
You ask them to enter their email address on the [Sign in with
SSO](https://app.supabase.com/sign-in-sso) page.
If sign in is not working correctly, please reach out to your support contact
at Supabase for further guidance.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+8 -7
View File
@@ -93,12 +93,13 @@ export const meta = {
</div>
export const resources = [
{
title: 'Examples',
hasLightIcon: true,
href: '/guides/resources/examples',
description: 'Official GitHub examples, curated content from the community, and more.',
},
// removing until the examples page is reworked
// {
// title: 'Examples',
// hasLightIcon: true,
// href: '/guides/resources/examples',
// description: 'Official GitHub examples, curated content from the community, and more.',
// },
{
title: 'Glossary',
hasLightIcon: true,
@@ -115,7 +116,7 @@ export const migrationGuides = [
description: 'Move your auth users from a Firebase project to a Supabase project.',
},
{
title: 'Firebase Data',
title: 'Firestore Data',
icon: '/docs/img/icons/firebase-icon',
href: '/guides/migrations/firestore-data',
description: 'Migrate the contents of a Firestore collection to a single PostgreSQL table.',
@@ -1,17 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/examples.mdx'
export const meta = {
id: 'examples',
title: 'Examples and Resources',
description: 'Examples you can use to get started with Supabase',
}
export const Page = ({ children }) => {
if (process.env.NEXT_PUBLIC_NEW_DOCS === 'true') {
return oldContent()
}
return <Layout meta={meta} children={children} />
}
export default Page
@@ -1,13 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
import oldContent from '~/pages/guides/hosting/overview.mdx'
export const meta = {
title: 'Migrating to Supabase',
description: 'Learn all about migrating to Supabase from other backend providers',
}
Hello world
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
Loaded 100 of 192 files, more files were not shown because too many files have changed in this diff. Show more