mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Merge branch 'master' of github.com:supabase/supabase
This commit is contained in:
commit
cdb66bee4f
192 files changed
+12515
-11969
No files matched your search
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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: [],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -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
|
||||
@@ -90,7 +90,7 @@
|
||||
},
|
||||
{
|
||||
"text": "SupaSquad",
|
||||
"url": "/docs/handbook/supasquad"
|
||||
"url": "/supasquad"
|
||||
},
|
||||
{
|
||||
"text": "DevTo",
|
||||
|
||||
@@ -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 />
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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:
|
||||
|
||||

|
||||
|
||||
### 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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
### 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
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
## 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,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.
|
||||
|
||||

|
||||
|
||||
## 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 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 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>`
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
- Once logged in, go to [discord.com/developers](https://discord.com/developers).
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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,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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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,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.
|
||||
|
||||

|
||||
|
||||
## 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)
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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 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`.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||

|
||||
|
||||
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.)
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
- 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.
|
||||
|
||||

|
||||
|
||||
## Add the redirect URI
|
||||
|
||||
- After selecting "Public integration", you should see an option to add "Redirect URIs".
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
- 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,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.
|
||||
|
||||

|
||||
|
||||
## 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)
|
||||
|
||||

|
||||
|
||||
## 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,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.
|
||||
|
||||

|
||||
|
||||
- Once logged in, go to the [Twitch Developer Console](https://dev.twitch.tv/console).
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
|
||||

|
||||
|
||||
- Click on `+ Register Your Application` at the top right.
|
||||
|
||||

|
||||
|
||||
- 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.
|
||||
|
||||

|
||||
|
||||
- Copy your Client ID.
|
||||
- Click `New Secret` to create a new Client Secret.
|
||||
- Copy your Client Secret.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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
|
||||

|
||||
|
||||
## 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
|
||||
|
||||

|
||||
|
||||
## Obtain the WorkOS Secret
|
||||
|
||||
Head over to the API Keys page and obtain the secret key.
|
||||
|
||||

|
||||
|
||||
## Connect a WorkOS Supported Identity Provider
|
||||
|
||||
Set up the identity provider by visiting the setup link.
|
||||
|
||||

|
||||
|
||||
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,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.
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
### 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
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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 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 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>`
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
- Once logged in, go to [discord.com/developers](https://discord.com/developers).
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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)
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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 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`.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||

|
||||
|
||||
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.)
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
- 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.
|
||||
|
||||

|
||||
|
||||
## Add the redirect URI
|
||||
|
||||
- After selecting "Public integration", you should see an option to add "Redirect URIs".
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
- 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.
|
||||
|
||||

|
||||
|
||||
## 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)
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
- Once logged in, go to the [Twitch Developer Console](https://dev.twitch.tv/console).
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
|
||||

|
||||
|
||||
- Click on `+ Register Your Application` at the top right.
|
||||
|
||||

|
||||
|
||||
- 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.
|
||||
|
||||

|
||||
|
||||
- Copy your Client ID.
|
||||
- Click `New Secret` to create a new Client Secret.
|
||||
- Copy your Client Secret.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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
|
||||

|
||||
|
||||
## 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
|
||||
|
||||

|
||||
|
||||
## Obtain the WorkOS Secret
|
||||
|
||||
Head over to the API Keys page and obtain the secret key.
|
||||
|
||||

|
||||
|
||||
## Connect a WorkOS Supported Identity Provider
|
||||
|
||||
Set up the identity provider by visiting the setup link.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
### 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
|
||||
@@ -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:
|
||||
|
||||

|
||||
|
||||
### 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
|
||||
Regular → Executable
+174
-7
@@ -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 />
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
## 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 />
|
||||
|
||||

|
||||
|
||||
### 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 />
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
## 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 />
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
## 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 />
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
## 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 />
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
## 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 />
|
||||
|
||||

|
||||
|
||||
### 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 />
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
## 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 />
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
## 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:
|
||||
|
||||

|
||||
|
||||
<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? */}
|
||||
|
||||
{/* [](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.
|
||||
|
||||

|
||||
|
||||
## 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 />
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
## 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 />
|
||||
|
||||

|
||||
|
||||
### 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`
|
||||
|
||||

|
||||
|
||||
## 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 />
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
## 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 />
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||

|
||||
|
||||
- [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
|
||||
@@ -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
|
||||
@@ -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_.
|
||||
|
||||

|
||||
|
||||
## Step 2: Choose Create your own application [#create-application]
|
||||
|
||||
You'll be using the custom enterprise application setup for Supabase.
|
||||
|
||||

|
||||
|
||||
## 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)_.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
All of the correct information should automatically populate the _Basic SAML
|
||||
Configuration_ screen as shown.
|
||||
|
||||

|
||||
|
||||
**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.**
|
||||
|
||||

|
||||
|
||||
## 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]
|
||||
|
||||

|
||||
|
||||
## Step 2: Choose Add custom SAML app [#add-custom-saml-app]
|
||||
|
||||
From the _Add app_ button in the toolbar choose _Add custom SAML app_.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
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_ |
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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 |
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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`
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
@@ -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
Reference in new issue
Block a user