diff --git a/apps/docs/content/guides/auth/social-login/auth-apple.mdx b/apps/docs/content/guides/auth/social-login/auth-apple.mdx index 2029bedf276..81ad16a50cc 100644 --- a/apps/docs/content/guides/auth/social-login/auth-apple.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-apple.mdx @@ -723,7 +723,6 @@ curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \ dependencies { implementation("io.github.jan-tennert.supabase:gotrue-kt:VERSION") implementation("io.github.jan-tennert.supabase:compose-auth:VERSION") - implementation("io.github.jan-tennert.supabase:compose-auth-ui:VERSION") // Optional, for UI components } ``` diff --git a/examples/edge-functions/app/package.json b/examples/edge-functions/app/package.json index 845bbe0a2fd..09944d5cd0e 100644 --- a/examples/edge-functions/app/package.json +++ b/examples/edge-functions/app/package.json @@ -3,7 +3,6 @@ "version": "0.1.0", "private": true, "dependencies": { - "@supabase/auth-ui-react": "^0.2.1", "@supabase/supabase-js": "^2", "@testing-library/jest-dom": "^5.16.3", "@testing-library/react": "^12.1.4", diff --git a/examples/edge-functions/app/src/App.js b/examples/edge-functions/app/src/App.js index 89f57cfe09d..84abc62da9e 100644 --- a/examples/edge-functions/app/src/App.js +++ b/examples/edge-functions/app/src/App.js @@ -1,14 +1,97 @@ import React, { useState } from 'react' -import { Auth, ThemeSupa } from '@supabase/auth-ui-react' import JSONInput from 'react-json-editor-ajrm' import locale from 'react-json-editor-ajrm/locale/en' -import { supabase } from './utils/supabaseClient' + import { functionsList } from './functionsList' +import { supabase } from './utils/supabaseClient' +import { useUser } from './utils/userContext' const sampleObject = { name: 'world' } +function AuthForm() { + const [mode, setMode] = useState('sign-in') + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const [message, setMessage] = useState(null) + + const handleSubmit = async (event) => { + event.preventDefault() + setLoading(true) + setError(null) + setMessage(null) + + if (mode === 'sign-in') { + const { error } = await supabase.auth.signInWithPassword({ email, password }) + if (error) setError(error.message) + } else { + const { data, error } = await supabase.auth.signUp({ + email, + password, + options: { emailRedirectTo: `${window.location.origin}/` }, + }) + if (error) { + setError(error.message) + } else if (data.user && !data.session) { + setMessage('Check your email for a confirmation link to complete sign up.') + } + } + + setLoading(false) + } + + return ( +
+ + + + + {error &&

{error}

} + {message &&

{message}

} +
+ ) +} + function App() { - const { user } = Auth.useUser() + const { user } = useUser() const [supaFunction, setSupaFunction] = useState(functionsList[0]) const [requestJson, setRequestJson] = useState(sampleObject) const [responseJson, setResponseJson] = useState({}) @@ -43,7 +126,7 @@ function App() { font-normal text-gray-700 transition - ease-in-out + ease-in-out focus:bg-white focus:text-gray-700 focus:outline-none" onChange={(e) => setSupaFunction(e.target.value)} > @@ -89,7 +172,7 @@ function App() { ) : ( - + )} diff --git a/examples/edge-functions/app/src/index.js b/examples/edge-functions/app/src/index.js index 36515be3349..01df3e243d0 100644 --- a/examples/edge-functions/app/src/index.js +++ b/examples/edge-functions/app/src/index.js @@ -1,15 +1,16 @@ import React from 'react' import ReactDOM from 'react-dom' + import './index.css' + import App from './App' -import { Auth } from '@supabase/auth-ui-react' -import { supabase } from './utils/supabaseClient' +import { UserProvider } from './utils/userContext' ReactDOM.render( - + - + , document.getElementById('root') ) diff --git a/examples/edge-functions/app/src/utils/userContext.js b/examples/edge-functions/app/src/utils/userContext.js new file mode 100644 index 00000000000..4e24c02d1bd --- /dev/null +++ b/examples/edge-functions/app/src/utils/userContext.js @@ -0,0 +1,27 @@ +import { createContext, useContext, useEffect, useState } from 'react' + +import { supabase } from './supabaseClient' + +const UserContext = createContext({ user: null, session: null }) + +export function UserProvider({ children }) { + const [session, setSession] = useState(null) + + useEffect(() => { + supabase.auth.getSession().then(({ data: { session } }) => setSession(session)) + + const { + data: { subscription }, + } = supabase.auth.onAuthStateChange((_event, session) => setSession(session)) + + return () => subscription.unsubscribe() + }, []) + + return ( + + {children} + + ) +} + +export const useUser = () => useContext(UserContext) diff --git a/examples/realtime/nextjs-auth-presence/package.json b/examples/realtime/nextjs-auth-presence/package.json index 9d28f2f94de..bb89971191b 100644 --- a/examples/realtime/nextjs-auth-presence/package.json +++ b/examples/realtime/nextjs-auth-presence/package.json @@ -10,7 +10,6 @@ "format": "prettier --cache --write \"pages/**/*.{js,jsx,ts,tsx,css,md,json}\"" }, "dependencies": { - "@supabase/auth-ui-react": "^0.4.7", "@supabase/ssr": "latest", "@supabase/supabase-js": "^2", "next": "^13.1.6", diff --git a/examples/realtime/nextjs-auth-presence/pages/login.tsx b/examples/realtime/nextjs-auth-presence/pages/login.tsx index c7f088366b0..70a75806856 100644 --- a/examples/realtime/nextjs-auth-presence/pages/login.tsx +++ b/examples/realtime/nextjs-auth-presence/pages/login.tsx @@ -1,20 +1,89 @@ -import { useSupabaseClient, useUser } from '@/lib/supabase-context' -import { Auth, ThemeSupa } from '@supabase/auth-ui-react' import type { NextPage } from 'next' +import { FormEvent, useState } from 'react' + import styles from '../styles/Home.module.css' +import { useSupabaseClient, useUser } from '@/lib/supabase-context' + +type Mode = 'sign-in' | 'sign-up' const LoginPage: NextPage = () => { const supabaseClient = useSupabaseClient() const user = useUser() + const [mode, setMode] = useState('sign-in') + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [loading, setLoading] = useState(false) + const [message, setMessage] = useState(null) + const [error, setError] = useState(null) + + const handleSubmit = async (event: FormEvent) => { + event.preventDefault() + setLoading(true) + setMessage(null) + setError(null) + + if (mode === 'sign-in') { + const { error } = await supabaseClient.auth.signInWithPassword({ email, password }) + if (error) setError(error.message) + } else { + const { data, error } = await supabaseClient.auth.signUp({ + email, + password, + options: { emailRedirectTo: `${window.location.origin}/` }, + }) + if (error) { + setError(error.message) + } else if (data.user && !data.session) { + setMessage('Check your email for a confirmation link to complete sign up.') + } + } + + setLoading(false) + } + if (!user) { return (
- +
+

{mode === 'sign-in' ? 'Sign in' : 'Sign up'}

+ + + + + {error &&

{error}

} + {message &&

{message}

} +
) } diff --git a/examples/slack-clone/nextjs-slack-clone-dotenvx/package.json b/examples/slack-clone/nextjs-slack-clone-dotenvx/package.json index c89512bb6fd..c63215b1d42 100644 --- a/examples/slack-clone/nextjs-slack-clone-dotenvx/package.json +++ b/examples/slack-clone/nextjs-slack-clone-dotenvx/package.json @@ -9,8 +9,6 @@ "start": "next start" }, "dependencies": { - "@supabase/auth-ui-react": "^0.4.7", - "@supabase/auth-ui-shared": "^0.1.8", "@supabase/supabase-js": "^2", "jwt-decode": "^4.0.0", "next": "latest", diff --git a/examples/slack-clone/nextjs-slack-clone-dotenvx/pages/index.js b/examples/slack-clone/nextjs-slack-clone-dotenvx/pages/index.js index 70bd7c767dc..2400b35db59 100644 --- a/examples/slack-clone/nextjs-slack-clone-dotenvx/pages/index.js +++ b/examples/slack-clone/nextjs-slack-clone-dotenvx/pages/index.js @@ -1,10 +1,14 @@ -import { useState, useEffect } from 'react' import { supabase } from 'lib/Store' -import { Auth } from '@supabase/auth-ui-react' -import { ThemeSupa } from '@supabase/auth-ui-shared' +import { useEffect, useState } from 'react' const Home = () => { const [session, setSession] = useState(null) + const [mode, setMode] = useState('sign-in') + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + const [message, setMessage] = useState(null) useEffect(() => { supabase.auth.getSession().then(({ data: { session } }) => { @@ -20,16 +24,113 @@ const Home = () => { return () => subscription.unsubscribe() }, []) + const handleSubmit = async (event) => { + event.preventDefault() + setIsLoading(true) + setError(null) + setMessage(null) + + if (mode === 'sign-in') { + const { error } = await supabase.auth.signInWithPassword({ email, password }) + if (error) setError(error.message) + } else { + const { data, error } = await supabase.auth.signUp({ + email, + password, + options: { emailRedirectTo: `${window.location.origin}/` }, + }) + if (error) { + setError(error.message) + } else if (data.user && !data.session) { + setMessage('Check your email for a confirmation link to complete sign up.') + } + } + + setIsLoading(false) + } + + const handleGitHubSignIn = async () => { + setIsLoading(true) + setError(null) + const { error } = await supabase.auth.signInWithOAuth({ + provider: 'github', + options: { redirectTo: `${window.location.origin}/` }, + }) + if (error) setError(error.message) + setIsLoading(false) + } + if (!session) { return (
-
- +
+

+ {mode === 'sign-in' ? 'Sign in' : 'Sign up'} +

+ +
+ + + +
+ +
+
+ or +
+
+ + + + + + {error &&

{error}

} + {message &&

{message}

}
diff --git a/examples/todo-list/nextjs-todo-list/package.json b/examples/todo-list/nextjs-todo-list/package.json index 2d65d0ede6a..bd1975a08cf 100644 --- a/examples/todo-list/nextjs-todo-list/package.json +++ b/examples/todo-list/nextjs-todo-list/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@next/font": "13.1.6", - "@supabase/auth-ui-react": "^0.4.7", "@supabase/ssr": "latest", "@supabase/supabase-js": "^2", "@types/node": "18.14.0", diff --git a/examples/todo-list/nextjs-todo-list/pages/index.tsx b/examples/todo-list/nextjs-todo-list/pages/index.tsx index 48f34bc54a1..46c25004c6d 100644 --- a/examples/todo-list/nextjs-todo-list/pages/index.tsx +++ b/examples/todo-list/nextjs-todo-list/pages/index.tsx @@ -1,12 +1,20 @@ -import Head from 'next/head' -import { supabase } from '@/lib/initSupabase' -import { Auth, ThemeSupa } from '@supabase/auth-ui-react' -import TodoList from '@/components/TodoList' import { Session } from '@supabase/supabase-js' -import { useEffect, useState } from 'react' +import Head from 'next/head' +import { FormEvent, useEffect, useState } from 'react' + +import TodoList from '@/components/TodoList' +import { supabase } from '@/lib/initSupabase' + +type Mode = 'sign-in' | 'sign-up' export default function Home() { const [session, setSession] = useState(null) + const [mode, setMode] = useState('sign-in') + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const [message, setMessage] = useState(null) useEffect(() => { supabase.auth.getSession().then(({ data: { session } }) => setSession(session)) @@ -18,6 +26,31 @@ export default function Home() { return () => subscription.unsubscribe() }, []) + const handleSubmit = async (event: FormEvent) => { + event.preventDefault() + setLoading(true) + setError(null) + setMessage(null) + + if (mode === 'sign-in') { + const { error } = await supabase.auth.signInWithPassword({ email, password }) + if (error) setError(error.message) + } else { + const { data, error } = await supabase.auth.signUp({ + email, + password, + options: { emailRedirectTo: `${window.location.origin}/` }, + }) + if (error) { + setError(error.message) + } else if (data.user && !data.session) { + setMessage('Check your email for a confirmation link to complete sign up.') + } + } + + setLoading(false) + } + return ( <> @@ -32,9 +65,55 @@ export default function Home() {
- Login + {mode === 'sign-in' ? 'Sign In' : 'Sign up'} - +
+ + + +
+ + {error &&

{error}

} + {message &&

{message}

}
diff --git a/examples/user-management/nextjs-user-management/README.md b/examples/user-management/nextjs-user-management/README.md index b11231a2632..894e1818a38 100644 --- a/examples/user-management/nextjs-user-management/README.md +++ b/examples/user-management/nextjs-user-management/README.md @@ -1,25 +1,36 @@ # Supabase Next.js Auth & User Management Starter -This example will set you up for a very common situation: users can sign up or sign in and then update their account with public profile information, including a profile image. +This example sets you up for a very common situation: users can sign up or sign in and then update their account with public profile information, including a profile image. This demonstrates how to use: - User signups using Supabase [Auth](https://supabase.com/auth). - - Supabase [SSR Auth for Next.js](https://supabase.com/docs/guides/auth/server-side/nextjs). - - Supabase [pre-built Auth UI for React](https://supabase.com/docs/guides/auth/auth-ui). -- User avatar images using Supabase [Storage](https://supabase.com/storage) -- Public profiles restricted with [Policies](https://supabase.com/docs/guides/auth#policies). -- Frontend using [Next.js](<[nextjs.org/](https://nextjs.org/)>). + - Supabase [SSR Auth for Next.js](https://supabase.com/docs/guides/auth/server-side/nextjs) with the Next.js App Router and Server Actions. +- User avatar images using Supabase [Storage](https://supabase.com/storage). +- Public profiles restricted with [Row Level Security policies](https://supabase.com/docs/guides/auth/row-level-security). +- Frontend using [Next.js](https://nextjs.org/) (App Router) with React 19 and Tailwind CSS v4. ## Technologies used - Frontend: - - [Next.js](https://github.com/vercel/next.js) - a React framework for production. - - [Supabase.js](https://supabase.com/docs/library/getting-started) for user management and realtime data syncing. - - Supabase [`@supabase/ssr`](https://supabase.com/docs/guides/auth/server-side/nextjs) for SSR auth. - - Supabase [pre-built Auth UI for React](https://supabase.com/docs/guides/auth/auth-ui). + - [Next.js](https://github.com/vercel/next.js) (App Router) — a React framework for production. + - [`@supabase/ssr`](https://supabase.com/docs/guides/auth/server-side/nextjs) for cookie-based SSR auth, used from both Server Components and Server Actions. + - [`@supabase/supabase-js`](https://supabase.com/docs/library/getting-started) for the browser client and realtime data. + - [Tailwind CSS v4](https://tailwindcss.com/) for styling. - Backend: - - [supabase.com/dashboard](https://supabase.com/dashboard/): hosted Postgres database with restful API for usage with Supabase.js. + - [supabase.com/dashboard](https://supabase.com/dashboard/) — hosted Postgres database with a REST API, Auth, and Storage. + - Local development via the [Supabase CLI](https://supabase.com/docs/guides/cli). + +## Project structure + +- `app/login/` — login and signup form. The form posts to Server Actions in `app/login/actions.ts` that call `supabase.auth.signInWithPassword()` and `supabase.auth.signUp()`. +- `app/account/` — protected profile page. Uses a Supabase server client to check the session and renders an account form with avatar upload. +- `app/auth/confirm/route.ts` — handles the email confirmation callback by verifying the OTP token and redirecting. +- `app/auth/signout/route.ts` — server route that signs the user out. +- `lib/supabase/client.ts` — browser client (`createBrowserClient`). +- `lib/supabase/server.ts` — server client (`createServerClient`) wired up to Next.js cookies. +- `supabase/migrations/` — database schema for the `profiles` table, RLS policies, the `handle_new_user` trigger, and the `avatars` storage bucket. +- `supabase/config.toml` — local Supabase configuration used by `npx supabase start`. ## Instant deploy @@ -27,43 +38,79 @@ The Vercel deployment will guide you through creating a Supabase account and pro [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fsupabase%2Fsupabase%2Ftree%2Fmaster%2Fexamples%2Fuser-management%2Fnextjs-user-management&project-name=supabase-nextjs-user-management&repository-name=supabase-nextjs-user-management&integration-ids=oac_VqOgBHqhEoFTPzGkPd7L0iH6&external-id=https%3A%2F%2Fgithub.com%2Fsupabase%2Fsupabase%2Ftree%2Fmaster%2Fexamples%2Fuser-management%2Fnextjs-user-management) -### 1. Create new project +## Run locally -Sign up to Supabase - [https://supabase.com/dashboard](https://supabase.com/dashboard) and create a new project. Wait for your database to start. +### 1. Install dependencies -### 2. Run "User Management" Quickstart +Requires Node.js 20+ and `npx` (bundled with npm). -Once your database has started, head over to your project's `SQL Editor` and run the "User Management Starter" quickstart. On the `SQL editor` page, scroll down until you see `User Management Starter: Sets up a public Profiles table which you can access with your API`. Click that, then click `RUN` to execute that query and create a new `profiles` table. When that's finished, head over to the `Table Editor` and see your new `profiles` table. +```bash +npm install +``` -### 3. Get the URL and Key +### 2. Start a local Supabase stack -Go to the Project Settings (the cog icon), open the API tab, and find your API URL and `anon` key, you'll need these in the next step. +The example includes a `supabase/` directory with the schema and config needed to run a local stack via the Supabase CLI. -The `anon` key is your client-side API key. It allows "anonymous access" to your database, until the user has logged in. Once they have logged in, the keys will switch to the user's own login token. This enables row level security for your data. Read more about this [below](#postgres-row-level-security). +```bash +npx supabase start +``` -![image](https://user-images.githubusercontent.com/10214025/88916245-528c2680-d298-11ea-8a71-708f93e1ce4f.png) +This boots Postgres, Auth, Storage, and Supabase Studio locally and runs the migrations in `supabase/migrations/`. When it finishes, it prints your local API URL and keys. -**_NOTE_**: The `secret` key has full access to your data, bypassing any security policies. These keys have to be kept secret and are meant to be used in server environments and never on a client or browser. +### 3. Configure environment variables -## Supabase details +Copy the development env template: -### Using a Remote Supabase Project +```bash +cp .env.example .env.local +``` -1. Create or select a project on [Supabase Dashboard](https://supabase.com/dashboard). -2. Copy and fill the dotenv template `cp .env.production.example .env.production` -3. Link the remote project to your local environment: +The defaults in `.env.example` already match the local stack (API URL `http://127.0.0.1:54321` and the demo publishable key). Update them if your local ports differ. + +### 4. Start the dev server + +```bash +npm run dev +``` + +Open [http://localhost:3000](http://localhost:3000) to use the app. + +## Using a remote Supabase project + +### 1. Create a project + +Sign up at [https://supabase.com/dashboard](https://supabase.com/dashboard) and create a new project. Wait for your database to start. + +### 2. Get the URL and publishable key + +Go to the Project Settings (the cog icon), open the API tab, and find your **Project URL** and **publishable key**. + +The `publishable` key is your client-side API key. It allows "anonymous access" to your database until the user logs in. Once they log in, the user's own JWT is used, which enables Row Level Security to scope data per user. Read more [below](#postgres-row-level-security). + +> **Note:** The `secret` (service role) key has full access to your data and bypasses all security policies. Keep it in server environments only — never expose it to the client or browser. + +### 3. Link and push the schema + +Copy the production env template and fill it in with your project URL, publishable key, and the URL(s) you want to allow as redirect targets: + +```bash +cp .env.production.example .env.production +``` + +Link your local checkout to the remote project: ```bash SUPABASE_ENV=production npx supabase@latest link --project-ref ``` -3. Sync the configuration: +Push the `supabase/config.toml` settings (Auth site URL, redirect URLs, etc.): ```bash SUPABASE_ENV=production npx supabase@latest config push ``` -4. Sync the database schema: +Push the database schema in `supabase/migrations/`: ```bash SUPABASE_ENV=production npx supabase@latest db push @@ -81,19 +128,19 @@ Supabase integrates seamlessly with Vercel's preview branches, giving each branc - `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY` 3. Create a new branch, make changes (e.g., update `max_frequency`), and push the branch to Git. - - Open a pull request to trigger Vercel + Supabase integration. + - Open a pull request to trigger the Vercel + Supabase integration. - Upon successful deployment, the preview environment reflects the changes. ![Preview Checks](https://github.com/user-attachments/assets/db688cc2-60fd-4463-bbed-e8ecc11b1a39) -### Postgres Row level security +## Postgres Row Level Security -This project uses very high-level Authorization using Postgres' Row Level Security. -When you start a Postgres database on Supabase, we populate it with an `auth` schema, and some helper functions. +This project uses high-level authorization via Postgres' Row Level Security. +When you start a Postgres database on Supabase, we populate it with an `auth` schema and some helper functions. When a user logs in, they are issued a JWT with the role `authenticated` and their UUID. We can use these details to provide fine-grained control over what each user can and cannot do. -This is a trimmed-down schema, with the policies: +The schema and policies that this example uses (see `supabase/migrations/20221017024722_init.sql`): ```sql -- Create a table for public profiles @@ -151,21 +198,21 @@ create policy "Anyone can update their own avatar." on storage.objects for update using ( auth.uid() = owner ) with check (bucket_id = 'avatars'); ``` -## More Supabase Examples & Resources +## More Supabase examples & resources -## Examples +### Examples These official examples are maintained by the Supabase team: - [Next.js Subscription Payments Starter](https://github.com/vercel/nextjs-subscription-payments) - [Next.js Slack Clone](https://github.com/supabase/supabase/tree/master/examples/slack-clone/nextjs-slack-clone) -- [Next.js 13 Data Fetching](https://github.com/supabase/supabase/tree/master/examples/caching/with-nextjs-13) +- [Next.js Data Fetching](https://github.com/supabase/supabase/tree/master/examples/caching/with-nextjs-13) - [And more...](https://github.com/supabase/supabase/tree/master/examples) -## Other resources +### Other resources - [[Docs] Next.js User Management Quickstart](https://supabase.com/docs/guides/getting-started/tutorials/with-nextjs) -- [[Egghead.io] Build a SaaS product with Next.js, Supabase and Stripe](https://egghead.io/courses/build-a-saas-product-with-next-js-supabase-and-stripe-61f2bc20) +- [[Docs] Server-Side Auth for Next.js](https://supabase.com/docs/guides/auth/server-side/nextjs) - [[Blog] Fetching and caching Supabase data in Next.js 13 Server Components](https://supabase.com/blog/fetching-and-caching-supabase-data-in-next-js-server-components) ## Authors