├── examples ├── sveltekit │ ├── .npmrc │ ├── static │ │ ├── favicon.png │ │ └── globals.css │ ├── .gitignore │ ├── .prettierrc │ ├── .env.example │ ├── vite.config.ts │ ├── src │ │ ├── routes │ │ │ ├── +layout.server.ts │ │ │ ├── profile │ │ │ │ ├── +page.ts │ │ │ │ └── +page.svelte │ │ │ ├── api │ │ │ │ └── protected-route │ │ │ │ │ └── +server.ts │ │ │ ├── protected-page │ │ │ │ ├── +page.ts │ │ │ │ └── +page.svelte │ │ │ ├── github-provider-token │ │ │ │ ├── +page.svelte │ │ │ │ └── +page.server.ts │ │ │ ├── +layout.ts │ │ │ ├── +layout.svelte │ │ │ └── +page.svelte │ │ ├── app.d.ts │ │ ├── app.html │ │ └── hooks.server.ts │ ├── svelte.config.js │ ├── .eslintrc.cjs │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── sveltekit-email-password │ ├── .npmrc │ ├── .gitignore │ ├── .prettierrc │ ├── static │ │ ├── favicon.png │ │ └── globals.css │ ├── .env.example │ ├── vite.config.ts │ ├── src │ │ ├── routes │ │ │ ├── +layout.server.ts │ │ │ ├── (app) │ │ │ │ ├── +page.ts │ │ │ │ ├── dashboard │ │ │ │ │ ├── +page.ts │ │ │ │ │ └── +page.svelte │ │ │ │ ├── +layout.svelte │ │ │ │ ├── logging-in │ │ │ │ │ └── +page@.svelte │ │ │ │ ├── +page.server.ts │ │ │ │ ├── signup │ │ │ │ │ ├── +page.server.ts │ │ │ │ │ └── +page.svelte │ │ │ │ └── +page.svelte │ │ │ ├── logout │ │ │ │ └── +page.server.ts │ │ │ ├── +layout.ts │ │ │ └── +layout.svelte │ │ ├── app.d.ts │ │ ├── app.html │ │ └── hooks.server.ts │ ├── svelte.config.js │ ├── .eslintrc.cjs │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── sveltekit-magic-link │ ├── .npmrc │ ├── .gitignore │ ├── .prettierrc │ ├── static │ │ ├── favicon.png │ │ └── globals.css │ ├── .env.example │ ├── vite.config.ts │ ├── src │ │ ├── routes │ │ │ ├── +layout.server.ts │ │ │ ├── +page.ts │ │ │ ├── logout │ │ │ │ └── +page.server.ts │ │ │ ├── dashboard │ │ │ │ ├── +page.ts │ │ │ │ └── +page.svelte │ │ │ ├── +layout.ts │ │ │ ├── +layout.svelte │ │ │ ├── +page.server.ts │ │ │ ├── Layout.svelte │ │ │ ├── logging-in │ │ │ │ └── +page.svelte │ │ │ └── +page.svelte │ │ ├── app.d.ts │ │ ├── app.html │ │ └── hooks.server.ts │ ├── svelte.config.js │ ├── .eslintrc.cjs │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── nextjs │ ├── .eslintrc.json │ ├── public │ │ ├── favicon.ico │ │ └── vercel.svg │ ├── next.config.js │ ├── pages │ │ ├── middleware-protected.tsx │ │ ├── api │ │ │ └── protected-route.ts │ │ ├── _app.tsx │ │ ├── profile.tsx │ │ ├── protected-page.tsx │ │ ├── github-provider-token.tsx │ │ └── index.tsx │ ├── .env.local.example │ ├── next-env.d.ts │ ├── styles │ │ ├── globals.css │ │ └── Home.module.css │ ├── .gitignore │ ├── tsconfig.json │ ├── package.json │ ├── middleware.ts │ ├── db_types.ts │ └── README.md ├── remix │ ├── .gitignore │ ├── .eslintrc │ ├── remix.env.d.ts │ ├── public │ │ └── favicon.ico │ ├── app │ │ ├── routes │ │ │ ├── __supabase │ │ │ │ ├── index.tsx │ │ │ │ ├── handle-supabase-auth.tsx │ │ │ │ ├── optional-session.tsx │ │ │ │ ├── realtime.tsx │ │ │ │ └── required-session.tsx │ │ │ └── __supabase.tsx │ │ ├── entry.client.tsx │ │ ├── root.tsx │ │ └── entry.server.tsx │ ├── .env.example │ ├── remix.config.js │ ├── utils │ │ └── supabase.server.ts │ ├── components │ │ ├── nav.tsx │ │ ├── realtime-posts.tsx │ │ └── login.tsx │ ├── tsconfig.json │ ├── package.json │ ├── db_types.ts │ ├── schema.sql │ └── README.md └── nextjs-server-components │ ├── .eslintrc.json │ ├── public │ ├── favicon.ico │ └── vercel.svg │ ├── .vscode │ └── settings.json │ ├── next.config.js │ ├── utils │ ├── supabase-browser.ts │ └── supabase-server.ts │ ├── .env.local.example │ ├── app │ ├── head.tsx │ ├── optional-session │ │ └── page.tsx │ ├── globals.css │ ├── required-session │ │ └── page.tsx │ ├── realtime │ │ ├── new-post.tsx │ │ ├── page.tsx │ │ └── realtime-posts.tsx │ ├── layout.tsx │ ├── page.tsx │ └── page.module.css │ ├── pages │ └── api │ │ └── hello.ts │ ├── .gitignore │ ├── tsconfig.json │ ├── package.json │ ├── db_types.ts │ ├── components │ ├── supabase-provider.tsx │ ├── supabase-listener.tsx │ └── login.tsx │ ├── middleware.tsx │ └── README.md ├── pnpm-workspace.yaml ├── .prettierrc ├── packages ├── shared │ ├── tsconfig.json │ ├── src │ │ ├── index.ts │ │ ├── utils │ │ │ ├── helpers.ts │ │ │ ├── index.ts │ │ │ ├── errors.ts │ │ │ └── cookies.ts │ │ ├── types.ts │ │ ├── supabase-browser.ts │ │ └── supabase-server.ts │ ├── tsup.config.ts │ ├── package.json │ └── CHANGELOG.md ├── config │ ├── CHANGELOG.md │ ├── eslint-preset.js │ └── package.json ├── tsconfig │ ├── README.md │ ├── package.json │ ├── CHANGELOG.md │ ├── react-library.json │ ├── svelte.json │ ├── base.json │ └── nextjs.json ├── nextjs │ ├── src │ │ ├── ambient.d.ts │ │ └── types.ts │ ├── tsconfig.json │ ├── README.md │ ├── tsup.config.ts │ ├── package.json │ └── CHANGELOG.md ├── remix │ ├── src │ │ ├── ambient.d.ts │ │ ├── index.ts │ │ └── utils │ │ │ ├── log.ts │ │ │ └── createSupabaseClient.ts │ ├── tsconfig.json │ ├── README.md │ ├── tsup.config.ts │ ├── CHANGELOG.md │ └── package.json ├── sveltekit │ ├── src │ │ ├── ambient.d.ts │ │ ├── index.ts │ │ ├── loadStorageAdapter.ts │ │ ├── serverStorageAdapter.ts │ │ ├── supabaseServerClient.ts │ │ └── supabaseLoadClient.ts │ ├── tsconfig.json │ ├── tsup.config.ts │ ├── package.json │ └── CHANGELOG.md └── react │ ├── README.md │ ├── src │ ├── index.tsx │ └── components │ │ └── SessionContext.tsx │ ├── tsconfig.json │ ├── tsup.config.ts │ ├── package.json │ └── CHANGELOG.md ├── .gitignore ├── tsconfig.json ├── .changeset ├── config.json └── README.md ├── .npmignore ├── typedoc.js ├── turbo.json ├── .github └── workflows │ ├── docs.yml │ └── release.yml ├── LICENSE ├── package.json ├── patches └── @changesets__assemble-release-plan@5.2.3.patch ├── development.md └── README.md /examples/sveltekit/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "examples/*" 3 | - "packages/*" 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none" 4 | } 5 | -------------------------------------------------------------------------------- /examples/nextjs/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /examples/remix/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /.cache 4 | /build 5 | /public/build 6 | .env 7 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /examples/remix/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@remix-run/eslint-config", "@remix-run/eslint-config/node"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/remix/remix.env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /examples/nextjs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BuilderIO/auth-helpers/main/examples/nextjs/public/favicon.ico -------------------------------------------------------------------------------- /examples/remix/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BuilderIO/auth-helpers/main/examples/remix/public/favicon.ico -------------------------------------------------------------------------------- /examples/sveltekit/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BuilderIO/auth-helpers/main/examples/sveltekit/static/favicon.png -------------------------------------------------------------------------------- /examples/remix/app/routes/__supabase/index.tsx: -------------------------------------------------------------------------------- 1 | export default function Index() { 2 | return

Welcome to the future!

; 3 | } 4 | -------------------------------------------------------------------------------- /examples/sveltekit/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | -------------------------------------------------------------------------------- /examples/sveltekit/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /packages/shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "include": ["src"], 4 | "exclude": ["node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/config/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # config 2 | 3 | ## 0.1.0 4 | 5 | ### Minor Changes 6 | 7 | - a3c2991: Initial release of new library version 8 | -------------------------------------------------------------------------------- /packages/tsconfig/README.md: -------------------------------------------------------------------------------- 1 | # `tsconfig` 2 | 3 | These are base shared `tsconfig.json`s from which all other `tsconfig.json`'s inherit from. 4 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /nbproject/ 2 | /.idea/* 3 | *.tmlanguage.cache 4 | *.tmPreferences.cache 5 | *.stTheme.cache 6 | *.sublime-workspace 7 | *.sublime-project 8 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BuilderIO/auth-helpers/main/examples/sveltekit-magic-link/static/favicon.png -------------------------------------------------------------------------------- /packages/nextjs/src/ambient.d.ts: -------------------------------------------------------------------------------- 1 | // these variables are defined by tsup 2 | declare const PACKAGE_NAME: string; 3 | declare const PACKAGE_VERSION: string; 4 | -------------------------------------------------------------------------------- /packages/remix/src/ambient.d.ts: -------------------------------------------------------------------------------- 1 | // these variables are defined by tsup 2 | declare const PACKAGE_NAME: string; 3 | declare const PACKAGE_VERSION: string; 4 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BuilderIO/auth-helpers/main/examples/nextjs-server-components/public/favicon.ico -------------------------------------------------------------------------------- /examples/sveltekit-email-password/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BuilderIO/auth-helpers/main/examples/sveltekit-email-password/static/favicon.png -------------------------------------------------------------------------------- /packages/sveltekit/src/ambient.d.ts: -------------------------------------------------------------------------------- 1 | // these variables are defined by tsup 2 | declare const PACKAGE_NAME: string; 3 | declare const PACKAGE_VERSION: string; 4 | -------------------------------------------------------------------------------- /packages/shared/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './supabase-browser'; 2 | export * from './supabase-server'; 3 | export * from './types'; 4 | export * from './utils'; 5 | -------------------------------------------------------------------------------- /examples/nextjs/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | } 5 | 6 | module.exports = nextConfig 7 | -------------------------------------------------------------------------------- /packages/sveltekit/src/index.ts: -------------------------------------------------------------------------------- 1 | export { createSupabaseLoadClient } from './supabaseLoadClient'; 2 | export { createSupabaseServerClient } from './supabaseServerClient'; 3 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true 4 | } -------------------------------------------------------------------------------- /examples/nextjs/pages/middleware-protected.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from 'next'; 2 | 3 | const Page: NextPage = () =>

Authenticated

; 4 | 5 | export default Page; 6 | -------------------------------------------------------------------------------- /packages/react/README.md: -------------------------------------------------------------------------------- 1 | # @supabase/auth-helpers-react 2 | 3 | This submodules provides components and hooks that can be used across all React based frameworks (e.g. Next.js, Remix). 4 | -------------------------------------------------------------------------------- /examples/remix/.env.example: -------------------------------------------------------------------------------- 1 | # Update these with your Supabase details from your project settings > API 2 | SUPABASE_URL=https://your-project.supabase.co 3 | SUPABASE_ANON_KEY=your-anon-key 4 | -------------------------------------------------------------------------------- /examples/remix/app/routes/__supabase/handle-supabase-auth.tsx: -------------------------------------------------------------------------------- 1 | // this is used to tell Remix to call active loaders 2 | // after a user signs in or out 3 | export const action = () => null; 4 | -------------------------------------------------------------------------------- /packages/react/src/index.tsx: -------------------------------------------------------------------------------- 1 | // Types 2 | export type { Session, User, SupabaseClient } from '@supabase/supabase-js'; 3 | 4 | // Methods & Components 5 | export * from './components/SessionContext'; 6 | -------------------------------------------------------------------------------- /examples/sveltekit/.env.example: -------------------------------------------------------------------------------- 1 | # Update these with your Supabase details from your project settings > API 2 | PUBLIC_SUPABASE_URL=https://your-project.supabase.co 3 | PUBLIC_SUPABASE_ANON_KEY=your-anon-key -------------------------------------------------------------------------------- /examples/nextjs-server-components/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | experimental: { 4 | appDir: true, 5 | }, 6 | } 7 | 8 | module.exports = nextConfig 9 | -------------------------------------------------------------------------------- /examples/nextjs/.env.local.example: -------------------------------------------------------------------------------- 1 | # Update these with your Supabase details from your project settings > API 2 | NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co 3 | NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/.env.example: -------------------------------------------------------------------------------- 1 | # Update these with your Supabase details from your project settings > API 2 | PUBLIC_SUPABASE_URL=https://your-project.supabase.co 3 | PUBLIC_SUPABASE_ANON_KEY=your-anon-key -------------------------------------------------------------------------------- /examples/sveltekit-email-password/.env.example: -------------------------------------------------------------------------------- 1 | # Update these with your Supabase details from your project settings > API 2 | PUBLIC_SUPABASE_URL=https://your-project.supabase.co 3 | PUBLIC_SUPABASE_ANON_KEY=your-anon-key -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "packages/react" }, 5 | { "path": "packages/nextjs" }, 6 | { "path": "packages/shared" }, 7 | { "path": "packages/sveltekit" } 8 | ] 9 | } -------------------------------------------------------------------------------- /examples/sveltekit/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import type { UserConfig } from 'vite'; 3 | 4 | const config: UserConfig = { 5 | plugins: [sveltekit()] 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /packages/shared/src/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | export function ensureArray(data: T | T[]): T[] { 2 | return Array.isArray(data) ? data : [data]; 3 | } 4 | 5 | export function isBrowser() { 6 | return typeof window !== 'undefined'; 7 | } 8 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import type { UserConfig } from 'vite'; 3 | 4 | const config: UserConfig = { 5 | plugins: [sveltekit()] 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /examples/sveltekit/src/routes/+layout.server.ts: -------------------------------------------------------------------------------- 1 | import type { LayoutServerLoad } from './$types'; 2 | 3 | export const load: LayoutServerLoad = async ({ locals: { getSession } }) => { 4 | return { 5 | session: getSession() 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/nextjs/src/types.ts: -------------------------------------------------------------------------------- 1 | export type AddParameters< 2 | TFunction extends (...args: any) => any, 3 | TParameters extends [...args: any] 4 | > = ( 5 | ...args: [...Parameters, ...TParameters] 6 | ) => ReturnType; 7 | -------------------------------------------------------------------------------- /packages/nextjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "compilerOptions": { 4 | "incremental": false, 5 | "importHelpers": true 6 | }, 7 | "include": ["src"], 8 | "exclude": ["node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/remix/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "compilerOptions": { 4 | "incremental": false, 5 | "importHelpers": true 6 | }, 7 | "include": ["src"], 8 | "exclude": ["node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /examples/nextjs/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import type { UserConfig } from 'vite'; 3 | 4 | const config: UserConfig = { 5 | plugins: [sveltekit()] 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /packages/sveltekit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "compilerOptions": { 4 | "incremental": false, 5 | "importHelpers": true 6 | }, 7 | "include": ["src"], 8 | "exclude": ["node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tsconfig", 3 | "version": "0.1.1", 4 | "private": true, 5 | "main": "index.js", 6 | "files": [ 7 | "base.json", 8 | "nextjs.json", 9 | "react-library.json" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/src/routes/+layout.server.ts: -------------------------------------------------------------------------------- 1 | import type { LayoutServerLoad } from './$types'; 2 | 3 | export const load: LayoutServerLoad = async ({ locals: { getSession } }) => { 4 | return { 5 | session: getSession() 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/src/routes/+layout.server.ts: -------------------------------------------------------------------------------- 1 | import type { LayoutServerLoad } from './$types'; 2 | 3 | export const load: LayoutServerLoad = async ({ locals: { getSession } }) => { 4 | return { 5 | session: getSession() 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/tsconfig/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # tsconfig 2 | 3 | ## 0.1.1 4 | 5 | ### Patch Changes 6 | 7 | - 7c60656: fix: removed old svelte reference from tsconfig 8 | 9 | ## 0.1.0 10 | 11 | ### Minor Changes 12 | 13 | - a3c2991: Initial release of new library version 14 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/utils/supabase-browser.ts: -------------------------------------------------------------------------------- 1 | import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'; 2 | import { Database } from '../db_types'; 3 | 4 | export const createBrowserClient = () => 5 | createBrowserSupabaseClient(); 6 | -------------------------------------------------------------------------------- /packages/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "compilerOptions": { 4 | "allowSyntheticDefaultImports": true, 5 | "allowJs": true 6 | }, 7 | "include": ["src"], 8 | "exclude": ["dist", "build", "node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/.env.local.example: -------------------------------------------------------------------------------- 1 | # Update these with your Supabase details from your project settings > API 2 | # https://app.supabase.com/project/_/settings/api 3 | NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co 4 | NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key 5 | -------------------------------------------------------------------------------- /examples/remix/remix.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@remix-run/dev').AppConfig} */ 2 | module.exports = { 3 | ignoredRouteFiles: ["**/.*"], 4 | // appDirectory: "app", 5 | // assetsBuildDirectory: "public/build", 6 | // serverBuildPath: "build/index.js", 7 | // publicPath: "/build/", 8 | }; 9 | -------------------------------------------------------------------------------- /packages/remix/src/index.ts: -------------------------------------------------------------------------------- 1 | // Methods 2 | export { 3 | createBrowserClient, 4 | createServerClient 5 | } from './utils/createSupabaseClient'; 6 | export { default as logger } from './utils/log'; 7 | 8 | // Types 9 | export type { Session, User, SupabaseClient } from '@supabase/supabase-js'; 10 | -------------------------------------------------------------------------------- /packages/config/eslint-preset.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["next", "prettier"], 3 | settings: { 4 | next: { 5 | rootDir: ["apps/*/", "packages/*/"], 6 | }, 7 | }, 8 | rules: { 9 | "@next/next/no-html-link-for-pages": "off", 10 | "react/jsx-key": "off", 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /packages/tsconfig/react-library.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "React Library", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "lib": ["ES6", "DOM"], 7 | "module": "commonjs", 8 | "target": "ES6", 9 | "jsx": "react-jsx" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/src/routes/+page.ts: -------------------------------------------------------------------------------- 1 | import type { PageLoad } from './$types'; 2 | import { redirect } from '@sveltejs/kit'; 3 | 4 | export const load: PageLoad = async ({ parent }) => { 5 | const { session } = await parent(); 6 | 7 | if (session) { 8 | throw redirect(303, '/dashboard'); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/src/routes/logout/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { redirect } from '@sveltejs/kit'; 2 | import type { Actions } from './$types'; 3 | 4 | export const actions: Actions = { 5 | async default({ locals: { supabase } }) { 6 | await supabase.auth.signOut(); 7 | throw redirect(303, '/'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/src/routes/(app)/+page.ts: -------------------------------------------------------------------------------- 1 | import type { PageLoad } from './$types'; 2 | import { redirect } from '@sveltejs/kit'; 3 | 4 | export const load: PageLoad = async ({ parent }) => { 5 | const { session } = await parent(); 6 | if (session) { 7 | throw redirect(303, '/dashboard'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/src/routes/logout/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { redirect } from '@sveltejs/kit'; 2 | import type { Actions } from './$types'; 3 | 4 | export const actions: Actions = { 5 | async default({ locals: { supabase } }) { 6 | await supabase.auth.signOut(); 7 | throw redirect(303, '/'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /packages/remix/README.md: -------------------------------------------------------------------------------- 1 | # @supabase/auth-helpers-remix (BETA) 2 | 3 | This submodule provides convenience helpers for implementing user authentication in Remix applications. 4 | 5 | - [Documentation](https://supabase.com/docs/guides/auth/auth-helpers/remix) 6 | - [Example](https://github.com/supabase/auth-helpers/tree/main/examples/remix) 7 | -------------------------------------------------------------------------------- /examples/sveltekit/src/routes/profile/+page.ts: -------------------------------------------------------------------------------- 1 | import { redirect } from '@sveltejs/kit'; 2 | import type { PageLoad } from './$types'; 3 | 4 | export const load: PageLoad = async ({ parent }) => { 5 | const { session } = await parent(); 6 | if (!session) { 7 | throw redirect(303, '/'); 8 | } 9 | return { user: session.user }; 10 | }; 11 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": ["@example/*"] 11 | } 12 | -------------------------------------------------------------------------------- /examples/nextjs/styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | -------------------------------------------------------------------------------- /examples/sveltekit/static/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/app/head.tsx: -------------------------------------------------------------------------------- 1 | export default function Head() { 2 | return ( 3 | <> 4 | Create Next App 5 | 6 | 7 | 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/utils/supabase-server.ts: -------------------------------------------------------------------------------- 1 | import { headers, cookies } from 'next/headers'; 2 | import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs'; 3 | import { Database } from '../db_types'; 4 | 5 | export const createServerClient = () => 6 | createServerComponentSupabaseClient({ 7 | headers, 8 | cookies 9 | }); 10 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/static/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/static/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | -------------------------------------------------------------------------------- /examples/sveltekit/src/routes/profile/+page.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |

10 | [Home] | [supabaseServerClient] 11 |

12 | 13 |
Hello {user.email}
14 |
{JSON.stringify(user, null, 2)}
15 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next'; 3 | 4 | type Data = { 5 | name: string; 6 | }; 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: 'John Doe' }); 13 | } 14 | -------------------------------------------------------------------------------- /examples/sveltekit/src/app.d.ts: -------------------------------------------------------------------------------- 1 | import { SupabaseClient, Session } from '@supabase/supabase-js'; 2 | 3 | declare global { 4 | namespace App { 5 | interface Locals { 6 | supabase: SupabaseClient; 7 | getSession(): Promise; 8 | } 9 | interface PageData { 10 | session: Session | null; 11 | } 12 | // interface Error {} 13 | // interface Platform {} 14 | } 15 | } 16 | 17 | export {}; 18 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/src/app.d.ts: -------------------------------------------------------------------------------- 1 | import { SupabaseClient, Session } from '@supabase/supabase-js'; 2 | 3 | declare global { 4 | namespace App { 5 | interface Locals { 6 | supabase: SupabaseClient; 7 | getSession(): Promise; 8 | } 9 | interface PageData { 10 | session: Session | null; 11 | } 12 | // interface Error {} 13 | // interface Platform {} 14 | } 15 | } 16 | 17 | export {}; 18 | -------------------------------------------------------------------------------- /packages/tsconfig/svelte.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Svelte", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "moduleResolution": "node", 7 | "module": "es2020", 8 | "lib": ["es2020", "DOM"], 9 | "target": "es2020", 10 | "importsNotUsedAsValues": "error", 11 | "isolatedModules": true, 12 | "resolveJsonModule": true, 13 | "sourceMap": true 14 | } 15 | } -------------------------------------------------------------------------------- /examples/sveltekit-email-password/src/app.d.ts: -------------------------------------------------------------------------------- 1 | import { SupabaseClient, Session } from '@supabase/supabase-js'; 2 | 3 | declare global { 4 | namespace App { 5 | interface Locals { 6 | supabase: SupabaseClient; 7 | getSession(): Promise; 8 | } 9 | interface PageData { 10 | session: Session | null; 11 | } 12 | // interface Error {} 13 | // interface Platform {} 14 | } 15 | } 16 | 17 | export {}; 18 | -------------------------------------------------------------------------------- /examples/sveltekit/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import preprocess from 'svelte-preprocess'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://github.com/sveltejs/svelte-preprocess 7 | // for more information about preprocessors 8 | preprocess: preprocess(), 9 | 10 | kit: { 11 | adapter: adapter() 12 | } 13 | }; 14 | 15 | export default config; 16 | -------------------------------------------------------------------------------- /packages/shared/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { CookieSerializeOptions } from 'cookie'; 2 | import type { SupabaseClientOptions } from '@supabase/supabase-js'; 3 | 4 | export type CookieOptions = { name?: string } & Pick< 5 | CookieSerializeOptions, 6 | 'domain' | 'secure' | 'path' | 'sameSite' | 'maxAge' 7 | >; 8 | 9 | export type SupabaseClientOptionsWithoutAuth = Omit< 10 | SupabaseClientOptions, 11 | 'auth' 12 | >; 13 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import preprocess from 'svelte-preprocess'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://github.com/sveltejs/svelte-preprocess 7 | // for more information about preprocessors 8 | preprocess: preprocess(), 9 | 10 | kit: { 11 | adapter: adapter() 12 | } 13 | }; 14 | 15 | export default config; 16 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import preprocess from 'svelte-preprocess'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://github.com/sveltejs/svelte-preprocess 7 | // for more information about preprocessors 8 | preprocess: preprocess(), 9 | 10 | kit: { 11 | adapter: adapter() 12 | } 13 | }; 14 | 15 | export default config; 16 | -------------------------------------------------------------------------------- /packages/nextjs/README.md: -------------------------------------------------------------------------------- 1 | # @supabase/auth-helpers-nextjs (BETA) 2 | 3 | This submodule provides convenience helpers for implementing user authentication in Next.js applications. 4 | 5 | - [Documentation](https://supabase.com/docs/guides/auth/auth-helpers/nextjs) 6 | - [Example](https://github.com/supabase/auth-helpers/tree/main/examples/nextjs) 7 | - [Server Components Example](https://github.com/supabase/auth-helpers/tree/main/examples/nextjs-server-components) 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.log 2 | npm-debug.log* 3 | 4 | # Coverage directory used by tools like istanbul 5 | coverage 6 | .nyc_output 7 | 8 | # Dependency directories 9 | node_modules 10 | 11 | # npm package lock 12 | package-lock.json 13 | yarn.lock 14 | 15 | # project files 16 | src 17 | test 18 | examples 19 | example 20 | example-next-js 21 | umd_temp 22 | CHANGELOG.md 23 | .travis.yml 24 | .editorconfig 25 | .eslintignore 26 | .eslintrc 27 | .babelrc 28 | .gitignore -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/src/routes/dashboard/+page.ts: -------------------------------------------------------------------------------- 1 | import type { PageLoad } from './$types'; 2 | import { redirect } from '@sveltejs/kit'; 3 | 4 | export const load: PageLoad = async ({ parent }) => { 5 | const { session, supabase } = await parent(); 6 | if (!session) { 7 | throw redirect(303, '/'); 8 | } 9 | const { data: testTable } = await supabase.from('test').select('*'); 10 | return { 11 | testTable, 12 | user: session.user 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /packages/react/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup'; 2 | 3 | export const tsup: Options = { 4 | dts: true, 5 | entryPoints: ['src/index.tsx'], 6 | external: ['react', /^@supabase\//], 7 | format: ['cjs'], 8 | // inject: ['src/react-shim.js'], 9 | // ! .cjs/.mjs doesn't work with Angular's webpack4 config by default! 10 | legacyOutput: false, 11 | sourcemap: true, 12 | splitting: false, 13 | bundle: true, 14 | clean: true 15 | }; 16 | -------------------------------------------------------------------------------- /examples/sveltekit/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | %sveltekit.head% 10 | 11 | 12 |
%sveltekit.body%
13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/sveltekit/src/routes/api/protected-route/+server.ts: -------------------------------------------------------------------------------- 1 | import { error, json } from '@sveltejs/kit'; 2 | import type { RequestHandler } from './$types'; 3 | 4 | export const GET: RequestHandler = async ({ locals: { supabase, getSession } }) => { 5 | const session = await getSession(); 6 | if (!session) { 7 | throw error(401, { message: 'Unauthorized' }); 8 | } 9 | const { data } = await supabase.from('test').select('*'); 10 | 11 | return json({ data }); 12 | }; 13 | -------------------------------------------------------------------------------- /examples/remix/utils/supabase.server.ts: -------------------------------------------------------------------------------- 1 | import { createServerClient as _createServerClient } from '@supabase/auth-helpers-remix'; 2 | 3 | import type { Database } from 'db_types'; 4 | 5 | export const createServerClient = ({ 6 | request, 7 | response 8 | }: { 9 | request: Request; 10 | response: Response; 11 | }) => 12 | _createServerClient( 13 | process.env.SUPABASE_URL!, 14 | process.env.SUPABASE_ANON_KEY!, 15 | { request, response } 16 | ); 17 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/src/routes/(app)/dashboard/+page.ts: -------------------------------------------------------------------------------- 1 | import type { PageLoad } from './$types'; 2 | import { redirect } from '@sveltejs/kit'; 3 | 4 | export const load: PageLoad = async ({ parent }) => { 5 | const { session, supabase } = await parent(); 6 | if (!session) { 7 | throw redirect(303, '/'); 8 | } 9 | 10 | const { data: testTable } = await supabase.from('test').select('*'); 11 | return { 12 | testTable, 13 | user: session.user 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /packages/shared/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup'; 2 | 3 | export const tsup: Options = { 4 | dts: true, 5 | entryPoints: ['src/index.ts'], 6 | external: ['react', 'next', /^@supabase\//], 7 | format: ['cjs', 'esm'], 8 | // inject: ['src/react-shim.js'], 9 | // ! .cjs/.mjs doesn't work with Angular's webpack4 config by default! 10 | legacyOutput: false, 11 | sourcemap: true, 12 | splitting: false, 13 | bundle: true, 14 | clean: true 15 | }; 16 | -------------------------------------------------------------------------------- /examples/sveltekit/src/routes/protected-page/+page.ts: -------------------------------------------------------------------------------- 1 | import { redirect } from '@sveltejs/kit'; 2 | import type { PageLoad } from './$types'; 3 | 4 | export const load: PageLoad = async ({ parent }) => { 5 | const { session, supabase } = await parent(); 6 | if (!session) { 7 | throw redirect(303, '/'); 8 | } 9 | const { data: tableData, error } = await supabase.from('test').select('*'); 10 | console.log(error); 11 | // console.log(tableData); 12 | 13 | return { tableData, user: session.user }; 14 | }; 15 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/src/routes/(app)/dashboard/+page.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |

Protected content for {user.email}

10 |

server-side fetched data with RLS:

11 |
{JSON.stringify(testTable, null, 2)}
12 |
13 |
14 |

user:

15 |
{JSON.stringify(user, null, 2)}
16 |
17 | -------------------------------------------------------------------------------- /examples/sveltekit/src/routes/github-provider-token/+page.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |

9 | [Home] | 10 | [withPageAuth] 11 |

12 | 13 |
Hello {user.email}
14 |

Data fetched with provider token:

15 |
{JSON.stringify(allRepos, null, 2)}
16 |

user:

17 |
{JSON.stringify(user, null, 2)}
18 | -------------------------------------------------------------------------------- /examples/remix/components/nav.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from '@remix-run/react'; 2 | 3 | export default function Nav() { 4 | return ( 5 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/app/optional-session/page.tsx: -------------------------------------------------------------------------------- 1 | import 'server-only'; 2 | 3 | import { createServerClient } from '../../utils/supabase-server'; 4 | 5 | // do not cache this page 6 | export const revalidate = 0; 7 | 8 | // this page will display with or without a user session 9 | export default async function OptionalSession() { 10 | const supabase = createServerClient(); 11 | const { data } = await supabase.from('posts').select('*'); 12 | 13 | return
{JSON.stringify({ data }, null, 2)}
; 14 | } 15 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | %sveltekit.head% 11 | 12 | 13 |
%sveltekit.body%
14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | %sveltekit.head% 11 | 12 | 13 |
%sveltekit.body%
14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/shared/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | AccessTokenNotFound, 3 | AuthHelperError, 4 | CookieNotFound, 5 | CookieNotSaved, 6 | CallbackUrlFailed, 7 | CookieNotParsed, 8 | JWTPayloadFailed, 9 | JWTInvalid, 10 | RefreshTokenNotFound, 11 | ProviderTokenNotFound, 12 | type ErrorPayload 13 | } from './errors'; 14 | 15 | export { 16 | parseCookies, 17 | serializeCookie, 18 | filterCookies, 19 | parseSupabaseCookie, 20 | stringifySupabaseSession 21 | } from './cookies'; 22 | export { ensureArray, isBrowser } from './helpers'; 23 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/app/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | 18 | @media (prefers-color-scheme: dark) { 19 | html { 20 | color-scheme: dark; 21 | } 22 | body { 23 | color: white; 24 | background: black; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/sveltekit/src/routes/protected-page/+page.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |

9 | [Home] | [withPageAuth] | [GitHub Token] 12 |

13 |
Protected content for {user.email}
14 |

server-side fetched data with RLS:

15 |
{JSON.stringify(tableData, null, 2)}
16 |

user:

17 |
{JSON.stringify(user, null, 2)}
18 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/src/routes/dashboard/+page.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 |
11 |

Protected content for {user.email}

12 |

server-side fetched data with RLS:

13 |
{JSON.stringify(testTable, null, 2)}
14 |
15 |
16 |

user:

17 |
{JSON.stringify(user, null, 2)}
18 |
19 |
20 | -------------------------------------------------------------------------------- /examples/sveltekit/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript') 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/app/required-session/page.tsx: -------------------------------------------------------------------------------- 1 | import 'server-only'; 2 | 3 | import { createServerClient } from '../../utils/supabase-server'; 4 | 5 | // do not cache this page 6 | export const revalidate = 0; 7 | 8 | // the user will be redirected to the landing page if they are not signed in 9 | // check middleware.tsx to see how this routing rule is set 10 | export default async function RequiredSession() { 11 | const supabase = createServerClient(); 12 | const { data } = await supabase.from('posts').select('*'); 13 | 14 | return
{JSON.stringify({ data }, null, 2)}
; 15 | } 16 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript') 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /examples/nextjs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | 39 | .vscode -------------------------------------------------------------------------------- /examples/nextjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript') 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /examples/sveltekit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | } 13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 14 | // 15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 16 | // from the referenced tsconfig.json - TypeScript does not merge them in 17 | } 18 | -------------------------------------------------------------------------------- /packages/tsconfig/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "composite": false, 6 | "declaration": true, 7 | "declarationMap": true, 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "inlineSources": false, 11 | "isolatedModules": true, 12 | "moduleResolution": "node", 13 | "noUnusedLocals": false, 14 | "noUnusedParameters": false, 15 | "preserveWatchOutput": true, 16 | "skipLibCheck": true, 17 | "strict": true 18 | }, 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | } 13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 14 | // 15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 16 | // from the referenced tsconfig.json - TypeScript does not merge them in 17 | } 18 | -------------------------------------------------------------------------------- /packages/nextjs/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup'; 2 | import pkg from './package.json'; 3 | 4 | export const tsup: Options = { 5 | dts: true, 6 | entryPoints: ['src/index.ts'], 7 | external: ['next', 'react', /^@supabase\//], 8 | format: ['cjs'], 9 | // inject: ['src/react-shim.js'], 10 | // ! .cjs/.mjs doesn't work with Angular's webpack4 config by default! 11 | legacyOutput: false, 12 | sourcemap: true, 13 | splitting: false, 14 | bundle: true, 15 | clean: true, 16 | define: { 17 | PACKAGE_NAME: JSON.stringify(pkg.name), 18 | PACKAGE_VERSION: JSON.stringify(pkg.version) 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /packages/remix/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup'; 2 | import pkg from './package.json'; 3 | 4 | export const tsup: Options = { 5 | dts: true, 6 | entryPoints: ['src/index.ts'], 7 | external: ['remix', 'react', /^@supabase\//], 8 | format: ['cjs'], 9 | // inject: ['src/react-shim.js'], 10 | // ! .cjs/.mjs doesn't work with Angular's webpack4 config by default! 11 | legacyOutput: false, 12 | sourcemap: true, 13 | splitting: false, 14 | bundle: true, 15 | clean: true, 16 | define: { 17 | PACKAGE_NAME: JSON.stringify(pkg.name), 18 | PACKAGE_VERSION: JSON.stringify(pkg.version) 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /examples/remix/app/entry.client.tsx: -------------------------------------------------------------------------------- 1 | import { RemixBrowser } from '@remix-run/react'; 2 | import { startTransition, StrictMode } from 'react'; 3 | import { hydrateRoot } from 'react-dom/client'; 4 | 5 | function hydrate() { 6 | startTransition(() => { 7 | hydrateRoot( 8 | document, 9 | 10 | 11 | 12 | ); 13 | }); 14 | } 15 | 16 | if (window.requestIdleCallback) { 17 | window.requestIdleCallback(hydrate); 18 | } else { 19 | // Safari doesn't support requestIdleCallback 20 | // https://caniuse.com/requestidlecallback 21 | window.setTimeout(hydrate, 1); 22 | } 23 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | } 13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 14 | // 15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 16 | // from the referenced tsconfig.json - TypeScript does not merge them in 17 | } 18 | -------------------------------------------------------------------------------- /packages/sveltekit/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup'; 2 | import pkg from './package.json'; 3 | 4 | export const tsup: Options = { 5 | dts: true, 6 | entryPoints: ['src/index.ts'], 7 | external: ['svelte', '@sveltejs/kit', /^@supabase\//], 8 | format: ['esm'], 9 | // inject: ['src/react-shim.js'], 10 | // ! .cjs/.mjs doesn't work with Angular's webpack4 config by default! 11 | legacyOutput: false, 12 | sourcemap: true, 13 | splitting: false, 14 | bundle: true, 15 | clean: true, 16 | define: { 17 | PACKAGE_NAME: JSON.stringify(pkg.name), 18 | PACKAGE_VERSION: JSON.stringify(pkg.version) 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /typedoc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: '@supabase/auth-helpers', 3 | out: './docs/', 4 | entryPoints: [ 5 | './packages/sveltekit/src/index.ts', 6 | './packages/nextjs/src/index.ts', 7 | './packages/shared/src/index.ts', 8 | './packages/react/src/index.tsx' 9 | ], 10 | entryPointStrategy: 'expand', 11 | exclude: [ 12 | 'packages/shared/src/utils/index.ts', 13 | 'packages/sveltekit/svelte.config.js', 14 | '**/node_modules/**', 15 | '**/dist/**', 16 | '**/packages/**/tsup.config.ts' 17 | ], 18 | excludeExternals: true, 19 | excludePrivate: true, 20 | hideGenerator: true, 21 | readme: 'none' 22 | }; 23 | -------------------------------------------------------------------------------- /examples/remix/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"], 3 | "compilerOptions": { 4 | "lib": ["DOM", "DOM.Iterable", "ES2019"], 5 | "isolatedModules": true, 6 | "esModuleInterop": true, 7 | "jsx": "react-jsx", 8 | "moduleResolution": "node", 9 | "resolveJsonModule": true, 10 | "target": "ES2019", 11 | "strict": true, 12 | "allowJs": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "baseUrl": ".", 15 | "paths": { 16 | "~/*": ["./app/*"] 17 | }, 18 | 19 | // Remix takes care of building everything in `remix build`. 20 | "noEmit": true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/tsconfig/nextjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Next.js", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "target": "es5", 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "noEmit": true, 13 | "incremental": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "jsx": "preserve" 19 | }, 20 | "include": ["src", "next-env.d.ts"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/src/routes/+layout.ts: -------------------------------------------------------------------------------- 1 | import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'; 2 | import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit'; 3 | import type { LayoutLoad } from './$types'; 4 | 5 | export const load: LayoutLoad = async ({ fetch, data, depends }) => { 6 | depends('supabase:auth'); 7 | 8 | const supabase = createSupabaseLoadClient({ 9 | supabaseUrl: PUBLIC_SUPABASE_URL, 10 | supabaseKey: PUBLIC_SUPABASE_ANON_KEY, 11 | event: { fetch }, 12 | serverSession: data.session 13 | }); 14 | 15 | const { 16 | data: { session } 17 | } = await supabase.auth.getSession(); 18 | 19 | return { supabase, session }; 20 | }; 21 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/src/routes/+layout.ts: -------------------------------------------------------------------------------- 1 | import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'; 2 | import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit'; 3 | import type { LayoutLoad } from './$types'; 4 | 5 | export const load: LayoutLoad = async ({ fetch, data, depends }) => { 6 | depends('supabase:auth'); 7 | 8 | const supabase = createSupabaseLoadClient({ 9 | supabaseUrl: PUBLIC_SUPABASE_URL, 10 | supabaseKey: PUBLIC_SUPABASE_ANON_KEY, 11 | event: { fetch }, 12 | serverSession: data.session 13 | }); 14 | 15 | const { 16 | data: { session } 17 | } = await supabase.auth.getSession(); 18 | 19 | return { supabase, session }; 20 | }; 21 | -------------------------------------------------------------------------------- /examples/remix/app/root.tsx: -------------------------------------------------------------------------------- 1 | import { MetaFunction } from '@remix-run/node'; 2 | import { 3 | Links, 4 | LiveReload, 5 | Meta, 6 | Outlet, 7 | Scripts, 8 | ScrollRestoration 9 | } from '@remix-run/react'; 10 | 11 | export const meta: MetaFunction = () => ({ 12 | charset: 'utf-8', 13 | title: 'New Remix App', 14 | viewport: 'width=device-width,initial-scale=1' 15 | }); 16 | 17 | export default function App() { 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | Supabase Auth Helpers Demo 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ] 22 | }, 23 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 24 | "exclude": ["node_modules"] 25 | } 26 | -------------------------------------------------------------------------------- /examples/sveltekit/src/routes/+layout.ts: -------------------------------------------------------------------------------- 1 | import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'; 2 | import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit'; 3 | import type { LayoutLoad } from './$types'; 4 | 5 | export const load: LayoutLoad = async ({ fetch, data, depends }) => { 6 | depends('supabase:auth'); 7 | 8 | const supabase = createSupabaseLoadClient({ 9 | supabaseUrl: PUBLIC_SUPABASE_URL, 10 | supabaseKey: PUBLIC_SUPABASE_ANON_KEY, 11 | event: { fetch }, 12 | serverSession: data.session 13 | }); 14 | 15 | const { 16 | data: { session } 17 | } = await supabase.auth.getSession(); 18 | 19 | console.log({ metadata: session?.user.user_metadata }); 20 | return { supabase, session }; 21 | }; 22 | -------------------------------------------------------------------------------- /packages/remix/src/utils/log.ts: -------------------------------------------------------------------------------- 1 | const dev = process.env.NODE_ENV !== 'production'; 2 | 3 | const logger = { 4 | log: (message?: any, ...optionalParams: any[]) => { 5 | dev ? console.log(message, ...optionalParams) : null; 6 | }, 7 | error: (message?: any, ...optionalParams: any[]) => { 8 | console.error(message, ...optionalParams); 9 | }, 10 | info: (message?: any, ...optionalParams: any[]) => { 11 | logger.log(message, ...optionalParams); 12 | }, 13 | debug: (message?: any, ...optionalParams: any[]) => { 14 | logger.log(message, ...optionalParams); 15 | }, 16 | warn: (message?: any, ...optionalParams: any[]) => { 17 | dev ? logger.error(message, ...optionalParams) : null; 18 | } 19 | }; 20 | 21 | export default logger; 22 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "pipeline": { 3 | "build": { 4 | "dependsOn": ["^build"], 5 | "outputs": ["dist/**", ".next/**", ".svelte-kit/**"] 6 | }, 7 | "build:example": { 8 | "dependsOn": ["^build", "^build:example"], 9 | "outputs": ["dist/**", ".next/**", ".svelte-kit/**"] 10 | }, 11 | "build:watch": { 12 | "dependsOn": ["^build:watch"], 13 | "outputs": ["dist/**", ".next/**", ".svelte-kit/**"] 14 | }, 15 | "clean:all": { 16 | "dependsOn": ["^clean:all"], 17 | "cache": false 18 | }, 19 | "lint": { 20 | "outputs": [] 21 | }, 22 | "dev": { 23 | "cache": false 24 | } 25 | }, 26 | "globalDependencies": [ 27 | "tsconfig.json", 28 | ".env.*" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 2 | 23 | 24 | 25 | Supabase Auth Helpers Demo 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/sveltekit/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 26 | 27 | {#if session} 28 | 29 | {/if} 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/src/routes/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { fail } from '@sveltejs/kit'; 2 | import type { Actions } from './$types'; 3 | 4 | export const actions: Actions = { 5 | async default({ request, url, locals: { supabase } }) { 6 | const formData = await request.formData(); 7 | const email = formData.get('email') as string; 8 | 9 | const { error } = await supabase.auth.signInWithOtp({ 10 | email, 11 | options: { 12 | emailRedirectTo: `${url.origin}/logging-in` 13 | } 14 | }); 15 | 16 | if (error) { 17 | return fail(400, { 18 | error: error.message, 19 | values: { email } 20 | }); 21 | } 22 | 23 | return { 24 | success: true, 25 | message: 'Please check your email for a magic link to log into the website.' 26 | }; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /examples/sveltekit/src/hooks.server.ts: -------------------------------------------------------------------------------- 1 | import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; 2 | import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit'; 3 | import type { Handle } from '@sveltejs/kit'; 4 | 5 | export const handle: Handle = async ({ event, resolve }) => { 6 | event.locals.supabase = createSupabaseServerClient({ 7 | supabaseUrl: PUBLIC_SUPABASE_URL, 8 | supabaseKey: PUBLIC_SUPABASE_ANON_KEY, 9 | event 10 | }); 11 | 12 | event.locals.getSession = async () => { 13 | const { 14 | data: { session } 15 | } = await event.locals.supabase.auth.getSession(); 16 | return session; 17 | }; 18 | 19 | return resolve(event, { 20 | filterSerializedResponseHeaders(name) { 21 | return name === 'content-range'; 22 | } 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/src/hooks.server.ts: -------------------------------------------------------------------------------- 1 | import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; 2 | import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit'; 3 | import type { Handle } from '@sveltejs/kit'; 4 | 5 | export const handle: Handle = async ({ event, resolve }) => { 6 | event.locals.supabase = createSupabaseServerClient({ 7 | supabaseUrl: PUBLIC_SUPABASE_URL, 8 | supabaseKey: PUBLIC_SUPABASE_ANON_KEY, 9 | event 10 | }); 11 | 12 | event.locals.getSession = async () => { 13 | const { 14 | data: { session } 15 | } = await event.locals.supabase.auth.getSession(); 16 | return session; 17 | }; 18 | 19 | return resolve(event, { 20 | filterSerializedResponseHeaders(name) { 21 | return name === 'content-range'; 22 | } 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/src/hooks.server.ts: -------------------------------------------------------------------------------- 1 | import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; 2 | import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit'; 3 | import type { Handle } from '@sveltejs/kit'; 4 | 5 | export const handle: Handle = async ({ event, resolve }) => { 6 | event.locals.supabase = createSupabaseServerClient({ 7 | supabaseUrl: PUBLIC_SUPABASE_URL, 8 | supabaseKey: PUBLIC_SUPABASE_ANON_KEY, 9 | event 10 | }); 11 | 12 | event.locals.getSession = async () => { 13 | const { 14 | data: { session } 15 | } = await event.locals.supabase.auth.getSession(); 16 | return session; 17 | }; 18 | 19 | return resolve(event, { 20 | filterSerializedResponseHeaders(name) { 21 | return name === 'content-range'; 22 | } 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@example/nextjs-server-components", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build:example": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@supabase/auth-helpers-nextjs": "workspace:*", 13 | "@types/node": "18.11.9", 14 | "@types/react": "18.0.25", 15 | "@types/react-dom": "18.0.8", 16 | "config": "workspace:*", 17 | "eslint": "8.27.0", 18 | "eslint-config-next": "13.0.2", 19 | "next": "13.0.2", 20 | "react": "18.2.0", 21 | "react-dom": "18.2.0", 22 | "server-only": "^0.0.1", 23 | "tsconfig": "workspace:*", 24 | "typescript": "4.8.4" 25 | }, 26 | "devDependencies": { 27 | "json5": ">=1.0.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/remix/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@example/remix", 3 | "private": true, 4 | "sideEffects": false, 5 | "scripts": { 6 | "build:example": "remix build", 7 | "dev": "PORT=3004 remix dev", 8 | "start": "PORT=3004 remix-serve build" 9 | }, 10 | "dependencies": { 11 | "@remix-run/node": "^1.7.6", 12 | "@remix-run/react": "^1.7.6", 13 | "@remix-run/serve": "^1.7.6", 14 | "@supabase/auth-helpers-remix": "workspace:*", 15 | "isbot": "^3.5.4", 16 | "react": "^18.2.0", 17 | "react-dom": "^18.2.0" 18 | }, 19 | "devDependencies": { 20 | "@remix-run/dev": "^1.7.6", 21 | "@remix-run/eslint-config": "^1.7.6", 22 | "@types/react": "^18.0.25", 23 | "@types/react-dom": "^18.0.8", 24 | "eslint": "^8.27.0", 25 | "typescript": "^4.8.4" 26 | }, 27 | "engines": { 28 | "node": ">=14" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/src/routes/Layout.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
15 | 27 | 28 |
29 | -------------------------------------------------------------------------------- /examples/nextjs/pages/api/protected-route.ts: -------------------------------------------------------------------------------- 1 | // pages/api/protected-route.ts 2 | import { NextApiHandler } from 'next'; 3 | import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs'; 4 | 5 | const ProtectedRoute: NextApiHandler = async (req, res) => { 6 | // Create authenticated Supabase Client 7 | const supabase = createServerSupabaseClient({ req, res }); 8 | // Check if we have a session 9 | const { 10 | data: { session } 11 | } = await supabase.auth.getSession(); 12 | 13 | if (!session) 14 | return res.status(401).json({ 15 | error: 'not_authenticated', 16 | description: 17 | 'The user does not have an active session or is not authenticated' 18 | }); 19 | 20 | // Run queries with RLS on the server 21 | const { data } = await supabase.from('users').select('*'); 22 | res.json(data); 23 | }; 24 | 25 | export default ProtectedRoute; 26 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/app/realtime/new-post.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useSupabase } from '../../components/supabase-provider'; 4 | 5 | export default function NewPost() { 6 | const { supabase, session } = useSupabase(); 7 | 8 | const handleSubmit = async (e: React.SyntheticEvent) => { 9 | e.preventDefault(); 10 | const target = e.target as typeof e.target & { 11 | post: { value: string }; 12 | }; 13 | const post = target.post.value; 14 | 15 | await supabase.from('posts').insert({ content: post }); 16 | // no need to refresh, as we are subscribed to db changes in `./realtime-posts.tsx` 17 | }; 18 | 19 | return session ? ( 20 | <> 21 |
22 | 23 | 24 |
25 | 26 | ) : ( 27 |

Sign in to see posts

28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/app/realtime/page.tsx: -------------------------------------------------------------------------------- 1 | import 'server-only'; 2 | 3 | import { createServerClient } from '../../utils/supabase-server'; 4 | import NewPost from './new-post'; 5 | import RealtimePosts from './realtime-posts'; 6 | 7 | // do not cache this page 8 | export const revalidate = 0; 9 | 10 | // this component fetches the current posts server-side 11 | // and subscribes to new posts client-side 12 | export default async function Realtime() { 13 | const supabase = createServerClient(); 14 | const { data } = await supabase.from('posts').select('*'); 15 | 16 | // data can be passed from server components to client components 17 | // this allows us to fetch the initial posts before rendering the page 18 | // our component will then subscribe to new posts client-side 19 | return ( 20 | <> 21 | 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /examples/remix/db_types.ts: -------------------------------------------------------------------------------- 1 | export type Json = 2 | | string 3 | | number 4 | | boolean 5 | | null 6 | | { [key: string]: Json } 7 | | Json[]; 8 | 9 | export interface Database { 10 | public: { 11 | Tables: { 12 | posts: { 13 | Row: { 14 | id: string; 15 | created_at: string; 16 | content: string; 17 | user_id: string; 18 | }; 19 | Insert: { 20 | id?: string; 21 | created_at?: string; 22 | content: string; 23 | user_id?: string; 24 | }; 25 | Update: { 26 | id?: string; 27 | created_at?: string; 28 | content?: string; 29 | user_id?: string; 30 | }; 31 | }; 32 | }; 33 | Views: { 34 | [_ in never]: never; 35 | }; 36 | Functions: { 37 | [_ in never]: never; 38 | }; 39 | Enums: { 40 | [_ in never]: never; 41 | }; 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/db_types.ts: -------------------------------------------------------------------------------- 1 | export type Json = 2 | | string 3 | | number 4 | | boolean 5 | | null 6 | | { [key: string]: Json } 7 | | Json[]; 8 | 9 | export interface Database { 10 | public: { 11 | Tables: { 12 | posts: { 13 | Row: { 14 | id: string; 15 | created_at: string; 16 | content: string; 17 | user_id: string; 18 | }; 19 | Insert: { 20 | id?: string; 21 | created_at?: string; 22 | content: string; 23 | user_id?: string; 24 | }; 25 | Update: { 26 | id?: string; 27 | created_at?: string; 28 | content?: string; 29 | user_id?: string; 30 | }; 31 | }; 32 | }; 33 | Views: { 34 | [_ in never]: never; 35 | }; 36 | Functions: { 37 | [_ in never]: never; 38 | }; 39 | Enums: { 40 | [_ in never]: never; 41 | }; 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /examples/sveltekit/src/routes/github-provider-token/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { redirect } from '@sveltejs/kit'; 2 | import type { PageServerLoad } from './$types'; 3 | 4 | interface GitHubOutput { 5 | total_count: number; 6 | incomplete_results: boolean; 7 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 8 | items: any[]; 9 | } 10 | 11 | export const load: PageServerLoad = async ({ locals: { getSession } }) => { 12 | const session = await getSession(); 13 | if (!session) { 14 | throw redirect(303, '/'); 15 | } 16 | 17 | const providerToken = session.provider_token; 18 | const userId = session.user.user_metadata.user_name; 19 | const allRepos: GitHubOutput = await fetch( 20 | `https://api.github.com/search/repositories?q=user:${userId}`, 21 | { 22 | method: 'GET', 23 | headers: { 24 | Authorization: `token ${providerToken}` 25 | } 26 | } 27 | ).then((res) => res.json()); 28 | 29 | return { 30 | user: session.user, 31 | allRepos 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/components/supabase-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import type { Session } from '@supabase/auth-helpers-nextjs'; 4 | import { createContext, useContext, useState } from 'react'; 5 | import type { TypedSupabaseClient } from '../app/layout'; 6 | import { createBrowserClient } from '../utils/supabase-browser'; 7 | 8 | type MaybeSession = Session | null; 9 | 10 | type SupabaseContext = { 11 | supabase: TypedSupabaseClient; 12 | session: MaybeSession; 13 | }; 14 | 15 | // @ts-ignore 16 | const Context = createContext(); 17 | 18 | export default function SupabaseProvider({ 19 | children, 20 | session 21 | }: { 22 | children: React.ReactNode; 23 | session: MaybeSession; 24 | }) { 25 | const [supabase] = useState(() => createBrowserClient()); 26 | 27 | return ( 28 | 29 | <>{children} 30 | 31 | ); 32 | } 33 | 34 | export const useSupabase = () => useContext(Context); 35 | -------------------------------------------------------------------------------- /packages/config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "config", 3 | "version": "0.1.0", 4 | "private": true, 5 | "main": "index.js", 6 | "license": "MIT", 7 | "files": [ 8 | "eslint-preset.js" 9 | ], 10 | "scripts": { 11 | "clean:all": "rimraf node_modules" 12 | }, 13 | "dependencies": { 14 | "eslint-config-next": "^12.0.8", 15 | "eslint-config-prettier": "^8.3.0", 16 | "eslint-plugin-react": "7.28.0" 17 | }, 18 | "devDependencies": { 19 | "eslint": ">=7.23.0 <8.0.0 || >=8.0.0-0 <8.0.0 || >=8.0.0 <9.0.0", 20 | "next": ">=10.2.0", 21 | "react": ">=17.0.2 <18.0.0 || >=18.0.0-0 <19.0.0", 22 | "react-dom": "^17.0.2 || ^18.0.0-0", 23 | "rimraf": "^3.0.2", 24 | "typescript": ">=3.3.1" 25 | }, 26 | "peerDependencies": { 27 | "eslint": ">=7.23.0 <8.0.0 || >=8.0.0-0 <8.0.0 || >=8.0.0 <9.0.0", 28 | "next": ">=10.2.0", 29 | "react": ">=17.0.2 <18.0.0 || >=18.0.0-0 <19.0.0", 30 | "react-dom": "^17.0.2 || ^18.0.0-0", 31 | "typescript": ">=3.3.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/middleware.tsx: -------------------------------------------------------------------------------- 1 | import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs'; 2 | import { NextResponse } from 'next/server'; 3 | import type { NextRequest } from 'next/server'; 4 | 5 | // this middleware refreshes the user's session and must be run 6 | // for any Server Component route that uses `createServerComponentSupabaseClient` 7 | export async function middleware(req: NextRequest) { 8 | const res = NextResponse.next(); 9 | 10 | const supabase = createMiddlewareSupabaseClient({ req, res }); 11 | 12 | const { 13 | data: { session } 14 | } = await supabase.auth.getSession(); 15 | 16 | if (!session && req.nextUrl.pathname.startsWith('/required-session')) { 17 | // Auth condition not met, redirect to home page. 18 | const redirectUrl = req.nextUrl.clone(); 19 | redirectUrl.pathname = '/'; 20 | redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname); 21 | return NextResponse.redirect(redirectUrl); 22 | } 23 | 24 | return res; 25 | } 26 | -------------------------------------------------------------------------------- /packages/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@supabase/auth-helpers-react", 3 | "version": "0.3.1", 4 | "main": "dist/index.js", 5 | "types": "dist/index.d.ts", 6 | "publishConfig": { 7 | "access": "public" 8 | }, 9 | "scripts": { 10 | "build": "tsup", 11 | "clean:all": "rimraf dist node_modules" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/supabase/auth-helpers.git" 16 | }, 17 | "keywords": [ 18 | "Supabase", 19 | "Auth", 20 | "React" 21 | ], 22 | "author": "Supabase", 23 | "license": "MIT", 24 | "devDependencies": { 25 | "@supabase/supabase-js": "2.0.4", 26 | "@types/react": "^17.0.20", 27 | "@types/react-dom": "^17.0.9", 28 | "config": "workspace:*", 29 | "react": "^17.0.2", 30 | "react-dom": "^17.0.2", 31 | "rimraf": "^4.1.1", 32 | "tsconfig": "workspace:*", 33 | "tsup": "^6.5.0", 34 | "typescript": "^4.9.4" 35 | }, 36 | "peerDependencies": { 37 | "@supabase/supabase-js": "^2.0.4" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@example/nextjs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build:example": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "clean:all": "rimraf .next node_modules" 11 | }, 12 | "dependencies": { 13 | "@supabase/auth-helpers-nextjs": "workspace:*", 14 | "@supabase/auth-helpers-react": "workspace:*", 15 | "@supabase/auth-ui-react": "^0.2.6", 16 | "next": "^12.2.5", 17 | "react": "17.0.2", 18 | "react-dom": "17.0.2" 19 | }, 20 | "devDependencies": { 21 | "@babel/core": ">=7.0.0 <8.0.0", 22 | "@types/node": "^17.0.12", 23 | "@types/react": "17.0.37", 24 | "config": "workspace:*", 25 | "encoding": "^0.1.13", 26 | "eslint": "7.32.0", 27 | "next-transpile-modules": "9.0.0", 28 | "rimraf": "^3.0.2", 29 | "tsconfig": "workspace:*", 30 | "typescript": "^4.5.3" 31 | }, 32 | "peerDependencies": { 33 | "@babel/core": ">=7.0.0 <8.0.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | jobs: 10 | docs: 11 | name: Publish docs / OS ${{ matrix.os }} / Node ${{ matrix.node }} 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest] 15 | node: ['16'] 16 | 17 | runs-on: ${{ matrix.os }} 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | 22 | - uses: pnpm/action-setup@v2.2.3 23 | with: 24 | version: 8.1.0 25 | 26 | - name: Set up Node 27 | uses: actions/setup-node@v1 28 | with: 29 | node-version: ${{ matrix.node }} 30 | cache: pnpm 31 | 32 | - run: | 33 | pnpm install --frozen-lockfile 34 | pnpm run build 35 | pnpm run docs 36 | 37 | - name: Publish 38 | uses: peaceiris/actions-gh-pages@v3 39 | with: 40 | github_token: ${{ secrets.GITHUB_TOKEN }} 41 | publish_dir: docs 42 | force_orphan: true 43 | commit_message: 'docs: update' 44 | -------------------------------------------------------------------------------- /examples/nextjs/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Supabase 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/remix/components/realtime-posts.tsx: -------------------------------------------------------------------------------- 1 | import { useOutletContext } from '@remix-run/react'; 2 | import { useEffect, useState } from 'react'; 3 | 4 | import type { SupabaseContext } from '~/routes/__supabase'; 5 | import type { Database } from 'db_types'; 6 | 7 | type Post = Database['public']['Tables']['posts']['Row']; 8 | 9 | export default function RealtimePosts({ 10 | serverPosts 11 | }: { 12 | serverPosts: Post[]; 13 | }) { 14 | const [posts, setPosts] = useState(serverPosts); 15 | const { supabase } = useOutletContext(); 16 | 17 | useEffect(() => { 18 | setPosts(serverPosts); 19 | }, [serverPosts]); 20 | 21 | useEffect(() => { 22 | const channel = supabase 23 | .channel('*') 24 | .on( 25 | 'postgres_changes', 26 | { event: 'INSERT', schema: 'public', table: 'posts' }, 27 | (payload) => setPosts([...posts, payload.new as Post]) 28 | ) 29 | .subscribe(); 30 | 31 | return () => { 32 | supabase.removeChannel(channel); 33 | }; 34 | }, [supabase, posts, setPosts]); 35 | 36 | return
{JSON.stringify(posts, null, 2)}
; 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | jobs: 10 | release: 11 | name: Release / Node ${{ matrix.node }} 12 | strategy: 13 | matrix: 14 | node: ['16'] 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - uses: pnpm/action-setup@v2.2.3 22 | with: 23 | version: 8.1.0 24 | 25 | - name: Set up Node 26 | uses: actions/setup-node@v2 27 | with: 28 | node-version: ${{ matrix.node }} 29 | cache: pnpm 30 | 31 | - run: | 32 | pnpm install --frozen-lockfile 33 | pnpm run build 34 | 35 | - name: Create a release 36 | id: changesets 37 | uses: changesets/action@v1 38 | with: 39 | version: pnpm ci:version 40 | commit: 'chore: update versions' 41 | title: 'chore: update versions' 42 | publish: pnpm ci:release 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 46 | -------------------------------------------------------------------------------- /examples/nextjs/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | import { 3 | createBrowserSupabaseClient, 4 | Session 5 | } from '@supabase/auth-helpers-nextjs'; 6 | import { SessionContextProvider } from '@supabase/auth-helpers-react'; 7 | import type { AppProps } from 'next/app'; 8 | import { useState } from 'react'; 9 | import { Database } from '../db_types'; 10 | import '../styles/globals.css'; 11 | 12 | function MyApp({ 13 | Component, 14 | pageProps 15 | }: AppProps<{ initialSession: Session }>) { 16 | const router = useRouter(); 17 | const [supabaseClient] = useState(() => 18 | createBrowserSupabaseClient() 19 | ); 20 | 21 | return ( 22 | 26 | 34 | 35 | 36 | 37 | ); 38 | } 39 | 40 | export default MyApp; 41 | -------------------------------------------------------------------------------- /examples/sveltekit/README.md: -------------------------------------------------------------------------------- 1 | # create-svelte 2 | 3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). 4 | 5 | ## Creating a project 6 | 7 | If you're seeing this, you've probably already done this step. Congrats! 8 | 9 | ```bash 10 | # create a new project in the current directory 11 | npm init svelte@next 12 | 13 | # create a new project in my-app 14 | npm init svelte@next my-app 15 | ``` 16 | 17 | > Note: the `@next` is temporary 18 | 19 | ## Developing 20 | 21 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 22 | 23 | ```bash 24 | npm run dev 25 | 26 | # or start the server and open the app in a new browser tab 27 | npm run dev -- --open 28 | ``` 29 | 30 | ## Building 31 | 32 | To create a production version of your app: 33 | 34 | ```bash 35 | npm run build 36 | ``` 37 | 38 | You can preview the production build with `npm run preview`. 39 | 40 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. 41 | -------------------------------------------------------------------------------- /examples/remix/app/routes/__supabase/optional-session.tsx: -------------------------------------------------------------------------------- 1 | import { json } from '@remix-run/node'; 2 | import { useLoaderData } from '@remix-run/react'; 3 | import { createServerClient } from 'utils/supabase.server'; 4 | 5 | import type { LoaderArgs } from '@remix-run/node'; 6 | 7 | export const loader = async ({ request }: LoaderArgs) => { 8 | const response = new Response(); 9 | const supabase = createServerClient({ request, response }); 10 | 11 | const { 12 | data: { session } 13 | } = await supabase.auth.getSession(); 14 | 15 | const { data } = await supabase.from('posts').select('*'); 16 | 17 | // in order for the set-cookie header to be set, 18 | // headers must be returned as part of the loader response 19 | return json( 20 | { data, session }, 21 | { 22 | headers: response.headers 23 | } 24 | ); 25 | }; 26 | 27 | export default function OptionalSession() { 28 | // by fetching the session in the loader, we ensure it is available 29 | // for first SSR render - no flashing of incorrect state 30 | const { data, session } = useLoaderData(); 31 | 32 | return
{JSON.stringify({ data, session }, null, 2)}
; 33 | } 34 | -------------------------------------------------------------------------------- /examples/nextjs/pages/profile.tsx: -------------------------------------------------------------------------------- 1 | // pages/profile.js 2 | import { 3 | createServerSupabaseClient, 4 | User 5 | } from '@supabase/auth-helpers-nextjs'; 6 | import { GetServerSidePropsContext } from 'next'; 7 | import Link from 'next/link'; 8 | 9 | export default function Profile({ user }: { user: User }) { 10 | return ( 11 | <> 12 |

13 | [Home] | [ 14 | server-side RLS] 15 |

16 |
Hello {user.email}
17 |
{JSON.stringify(user, null, 2)}
18 | 19 | ); 20 | } 21 | 22 | export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { 23 | // Create authenticated Supabase Client 24 | const supabase = createServerSupabaseClient(ctx); 25 | // Check if we have a session 26 | const { 27 | data: { session } 28 | } = await supabase.auth.getSession(); 29 | 30 | if (!session) 31 | return { 32 | redirect: { 33 | destination: '/', 34 | permanent: false 35 | } 36 | }; 37 | 38 | return { 39 | props: { 40 | initialSession: session, 41 | user: session.user 42 | } 43 | }; 44 | }; 45 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/src/routes/(app)/+layout.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | Email and Password Demo - Supabase Auth Helpers 23 | 24 | 25 |
26 | 38 | 39 | 40 |
41 | -------------------------------------------------------------------------------- /examples/nextjs/middleware.ts: -------------------------------------------------------------------------------- 1 | import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs'; 2 | import { NextResponse } from 'next/server'; 3 | import type { NextRequest } from 'next/server'; 4 | 5 | export async function middleware(req: NextRequest) { 6 | // We need to create a response and hand it to the supabase client to be able to modify the response headers. 7 | const res = NextResponse.next(); 8 | // Create authenticated Supabase Client 9 | const supabase = createMiddlewareSupabaseClient({ req, res }); 10 | // Check if we have a session 11 | const { 12 | data: { session } 13 | } = await supabase.auth.getSession(); 14 | 15 | // Check auth condition 16 | if (session?.user.email?.endsWith('@gmail.com')) { 17 | // Authentication successful, forward request to protected route. 18 | return res; 19 | } 20 | 21 | // Auth condition not met, redirect to home page. 22 | const redirectUrl = req.nextUrl.clone(); 23 | redirectUrl.pathname = '/'; 24 | redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname); 25 | return NextResponse.redirect(redirectUrl); 26 | } 27 | 28 | export const config = { 29 | matcher: '/middleware-protected' 30 | }; 31 | -------------------------------------------------------------------------------- /examples/remix/schema.sql: -------------------------------------------------------------------------------- 1 | -- This file can be used to create the database schema for this example. 2 | -- Copy and paste these commands to the `SQL Editor` tab in your Supabase dashboard, and click `RUN`. 3 | 4 | create table if not exists test ( 5 | id uuid default uuid_generate_v4() primary key, 6 | created_at timestamp with time zone default timezone('utc'::text, now()) not null, 7 | user_id uuid references auth.users not null 8 | ); 9 | 10 | alter table public.test 11 | enable row level security; 12 | 13 | create policy "Users can select their test records" on test 14 | as permissive for select 15 | to authenticated 16 | using (auth.uid() = user_id); 17 | 18 | create policy "Users can insert their own records." on test 19 | for insert with check (auth.uid() = user_id); 20 | 21 | 22 | -- Set up realtime 23 | begin; 24 | -- remove the supabase_realtime publication 25 | drop publication if exists supabase_realtime; 26 | 27 | -- re-create the supabase_realtime publication with no tables and only for insert 28 | create publication supabase_realtime with (publish = 'insert'); 29 | commit; 30 | 31 | -- add a table to the publication 32 | alter publication supabase_realtime add table test; -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/src/routes/logging-in/+page.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
16 | 17 |
18 | 19 |
20 | 21 | 22 |
23 |
24 | 25 | "Because as we know, there are known knowns; there are things we know we know. We also know 26 | there are known unknowns; that is to say we know there are some things we do not know. But 27 | there are also unknown unknowns—the ones we don't know we don't know" 28 | 29 |
30 |
31 |
32 | 33 | 38 | -------------------------------------------------------------------------------- /examples/remix/components/login.tsx: -------------------------------------------------------------------------------- 1 | import type { MaybeSession, TypedSupabaseClient } from '~/routes/__supabase'; 2 | 3 | export default function Login({ 4 | supabase, 5 | session 6 | }: { 7 | supabase: TypedSupabaseClient; 8 | session: MaybeSession; 9 | }) { 10 | const handleEmailLogin = async () => { 11 | const { error } = await supabase.auth.signInWithPassword({ 12 | email: 'jon@supabase.com', 13 | password: 'password' 14 | }); 15 | 16 | if (error) { 17 | console.log({ error }); 18 | } 19 | }; 20 | 21 | const handleGitHubLogin = async () => { 22 | const { error } = await supabase.auth.signInWithOAuth({ 23 | provider: 'github' 24 | }); 25 | 26 | if (error) { 27 | console.log({ error }); 28 | } 29 | }; 30 | 31 | const handleLogout = async () => { 32 | const { error } = await supabase.auth.signOut(); 33 | 34 | if (error) { 35 | console.log(error); 36 | } 37 | }; 38 | 39 | return session ? ( 40 | 41 | ) : ( 42 | <> 43 | 44 | 45 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/components/supabase-listener.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useRouter } from 'next/navigation'; 4 | import { useEffect } from 'react'; 5 | import { useSupabase } from './supabase-provider'; 6 | 7 | // this component handles refreshing server data when the user logs in or out 8 | // this method avoids the need to pass a session down to child components 9 | // in order to re-render when the user's session changes 10 | // #elegant! 11 | export default function SupabaseListener({ 12 | serverAccessToken 13 | }: { 14 | serverAccessToken?: string; 15 | }) { 16 | const { supabase } = useSupabase(); 17 | const router = useRouter(); 18 | 19 | useEffect(() => { 20 | const { 21 | data: { subscription } 22 | } = supabase.auth.onAuthStateChange((event, session) => { 23 | if (session?.access_token !== serverAccessToken) { 24 | // server and client are out of sync 25 | // reload the page to fetch fresh server data 26 | // https://beta.nextjs.org/docs/data-fetching/mutating 27 | router.refresh(); 28 | } 29 | }); 30 | 31 | return () => { 32 | subscription.unsubscribe(); 33 | }; 34 | }, [serverAccessToken, router, supabase]); 35 | 36 | return null; 37 | } 38 | -------------------------------------------------------------------------------- /packages/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@supabase/auth-helpers-shared", 3 | "version": "0.3.4", 4 | "main": "dist/index.js", 5 | "module": "dist/index.mjs", 6 | "types": "dist/index.d.ts", 7 | "publishConfig": { 8 | "access": "public" 9 | }, 10 | "files": [ 11 | "dist" 12 | ], 13 | "scripts": { 14 | "build": "tsup" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/supabase/auth-helpers.git" 19 | }, 20 | "keywords": [ 21 | "Supabase", 22 | "Auth", 23 | "Svelte Kit", 24 | "Svelte" 25 | ], 26 | "author": "Supabase", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/supabase/auth-helpers/issues" 30 | }, 31 | "homepage": "https://github.com/supabase/auth-helpers#readme", 32 | "devDependencies": { 33 | "@supabase/supabase-js": "2.0.4", 34 | "@types/cookie": "^0.5.1", 35 | "cookie": "^0.5.0", 36 | "next": "^12.1.5", 37 | "react": ">=17.0.2 <18.0.0 || >=18.0.0-0 <19.0.0", 38 | "react-dom": "^17.0.2 || ^18.0.0-0", 39 | "tsconfig": "workspace:*", 40 | "tsup": "^6.5.0" 41 | }, 42 | "dependencies": { 43 | "jose": "^4.14.0" 44 | }, 45 | "peerDependencies": { 46 | "@supabase/supabase-js": "^2.0.4" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/src/routes/(app)/logging-in/+page@.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 |
17 | 18 |
19 | 20 |
21 | 22 | 23 |
24 |
25 | 26 | "Because as we know, there are known knowns; there are things we know we know. We also know 27 | there are known unknowns; that is to say we know there are some things we do not know. But 28 | there are also unknown unknowns—the ones we don't know we don't know" 29 | 30 |
31 |
32 |
33 | 34 | 39 | -------------------------------------------------------------------------------- /examples/remix/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Remix! 2 | 3 | - [Remix Docs](https://remix.run/docs) 4 | 5 | ## Development 6 | 7 | From your terminal: 8 | 9 | ```sh 10 | npm run dev 11 | ``` 12 | 13 | This starts your app in development mode, rebuilding assets on file changes. 14 | 15 | ## Deployment 16 | 17 | First, build your app for production: 18 | 19 | ```sh 20 | npm run build 21 | ``` 22 | 23 | Then run the app in production mode: 24 | 25 | ```sh 26 | npm start 27 | ``` 28 | 29 | Now you'll need to pick a host to deploy it to. 30 | 31 | ### DIY 32 | 33 | If you're familiar with deploying node applications, the built-in Remix app server is production-ready. 34 | 35 | Make sure to deploy the output of `remix build` 36 | 37 | - `build/` 38 | - `public/build/` 39 | 40 | ### Using a Template 41 | 42 | When you ran `npx create-remix@latest` there were a few choices for hosting. You can run that again to create a new project, then copy over your `app/` folder to the new project that's pre-configured for your target server. 43 | 44 | ```sh 45 | cd .. 46 | # create a new project, and pick a pre-configured host 47 | npx create-remix@latest 48 | cd my-new-remix-app 49 | # remove the new project's app (not the old one!) 50 | rm -rf app 51 | # copy your app over 52 | cp -R ../my-old-remix-app/app app 53 | ``` 54 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/README.md: -------------------------------------------------------------------------------- 1 | # Supabase Magic Link login with SvelteKit 2 | 3 | ## Getting started 4 | 5 | Install the dependencies for this project by running the following command from this repository root directory in your terminal 6 | 7 | ```bash 8 | pnpm install 9 | ``` 10 | 11 | ## Project setup 12 | 13 | 1. Create a project on the Supabase dashboard 14 | 2. Get the `URL` and `anon` key from your [Settings > API](https://app.supabase.com/project/_/settings/api) section 15 | 3. Copy the `.env.example` file in this project and create a new `.env` file from it 16 | 4. Replace `PUBLIC_SUPABASE_URL` with the `URL` from step 2 and `PUBLIC_SUPABASE_ANON_KEY` with `anon` key from step 2 17 | 5. Copy the `SQL` below and paste it inside of the [SQL Editor](https://app.supabase.com/project/_/sql) section 18 | 19 | ```sql 20 | DROP TABLE IF EXISTS "public"."test"; 21 | 22 | -- Table Definition 23 | CREATE TABLE "public"."test" ( 24 | "id" int8 NOT NULL, 25 | "created_at" timestamptz DEFAULT now(), 26 | PRIMARY KEY ("id") 27 | ); 28 | ``` 29 | 30 | 6. Build the package that this example relies on using the following command 31 | 32 | ```bash 33 | pnpm build:sveltekit 34 | ``` 35 | 36 | 7. Run the following command from the repository root 37 | 38 | ```bash 39 | pnpm dev --filter=@example/sveltekit-magic-link -- --open 40 | ``` 41 | -------------------------------------------------------------------------------- /packages/nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@supabase/auth-helpers-nextjs", 3 | "version": "0.6.1", 4 | "description": "A collection of framework specific Auth utilities for working with Supabase.", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "publishConfig": { 8 | "access": "public" 9 | }, 10 | "scripts": { 11 | "build": "tsup", 12 | "clean:all": "rimraf dist node_modules" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/supabase/auth-helpers.git" 17 | }, 18 | "keywords": [ 19 | "Supabase", 20 | "Auth", 21 | "Nextjs" 22 | ], 23 | "author": "Supabase", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/supabase/auth-helpers/issues" 27 | }, 28 | "homepage": "https://github.com/supabase/auth-helpers/tree/main/packages/nextjs#readme", 29 | "devDependencies": { 30 | "@supabase/supabase-js": "2.0.4", 31 | "config": "workspace:*", 32 | "next": "^12.2.5", 33 | "react": "^18.0.0", 34 | "react-dom": "^18.0.0", 35 | "rimraf": "^4.1.1", 36 | "tsconfig": "workspace:*", 37 | "tslib": "^2.4.1", 38 | "tsup": "^6.5.0" 39 | }, 40 | "dependencies": { 41 | "@supabase/auth-helpers-shared": "workspace:*" 42 | }, 43 | "peerDependencies": { 44 | "@supabase/supabase-js": "^2.0.4" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/README.md: -------------------------------------------------------------------------------- 1 | # Supabase Email and Password Server-side login with SvelteKit 2 | 3 | ## Getting started 4 | 5 | Install the dependencies for this project by running the following command from this repository root directory in your terminal 6 | 7 | ```bash 8 | pnpm install 9 | ``` 10 | 11 | ## Project setup 12 | 13 | 1. Create a project on the Supabase dashboard 14 | 2. Get the `URL` and `anon` key from your [Settings > API](https://app.supabase.com/project/_/settings/api) section 15 | 3. Copy the `.env.example` file in this project and create a new `.env` file from it 16 | 4. Replace `PUBLIC_SUPABASE_URL` with the `URL` from step 2 and `PUBLIC_SUPABASE_ANON_KEY` with `anon` key from step 2 17 | 5. Copy the `SQL` below and paste it inside of the [SQL Editor](https://app.supabase.com/project/_/sql) section 18 | 19 | ```sql 20 | DROP TABLE IF EXISTS "public"."test"; 21 | 22 | -- Table Definition 23 | CREATE TABLE "public"."test" ( 24 | "id" int8 NOT NULL, 25 | "created_at" timestamptz DEFAULT now(), 26 | PRIMARY KEY ("id") 27 | ); 28 | ``` 29 | 30 | 6. Build the package that this example relies on using the following command 31 | 32 | ```bash 33 | pnpm build:sveltekit 34 | ``` 35 | 36 | 7. Run the following command from the repository root 37 | 38 | ```bash 39 | pnpm dev --filter=@example/sveltekit-email-password -- --open 40 | ``` 41 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/src/routes/(app)/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { AuthApiError } from '@supabase/supabase-js'; 2 | import { fail, redirect, type ActionFailure } from '@sveltejs/kit'; 3 | import type { Actions } from './$types'; 4 | 5 | export const actions: Actions = { 6 | async default({ 7 | request, 8 | locals: { supabase } 9 | }): Promise> { 10 | const formData = await request.formData(); 11 | 12 | const email = formData.get('email') as string; 13 | const password = formData.get('password') as string; 14 | 15 | if (!email) { 16 | return fail(400, { 17 | error: 'Please enter your email' 18 | }); 19 | } 20 | if (!password) { 21 | return fail(400, { 22 | error: 'Please enter your password', 23 | values: { 24 | email 25 | } 26 | }); 27 | } 28 | 29 | const { error } = await supabase.auth.signInWithPassword({ email, password }); 30 | 31 | if (error) { 32 | if (error instanceof AuthApiError && error.status === 400) { 33 | return fail(400, { 34 | error: 'Invalid credentials.', 35 | values: { 36 | email 37 | } 38 | }); 39 | } 40 | return fail(500, { 41 | error: 'Server error. Try again later.', 42 | values: { 43 | email 44 | } 45 | }); 46 | } 47 | 48 | throw redirect(303, '/dashboard'); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /packages/remix/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @supabase/auth-helpers-remix 2 | 3 | ## 0.1.9 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [04a7249] 8 | - Updated dependencies [04a7249] 9 | - @supabase/auth-helpers-shared@0.3.4 10 | 11 | ## 0.1.8 12 | 13 | ### Patch Changes 14 | 15 | - Updated dependencies [1ea258e] 16 | - @supabase/auth-helpers-shared@0.3.3 17 | 18 | ## 0.1.7 19 | 20 | ### Patch Changes 21 | 22 | - Updated dependencies [185e9cf] 23 | - @supabase/auth-helpers-shared@0.3.2 24 | 25 | ## 0.1.6 26 | 27 | ### Patch Changes 28 | 29 | - Updated dependencies [f86073d] 30 | - @supabase/auth-helpers-shared@0.3.1 31 | 32 | ## 0.1.5 33 | 34 | ### Patch Changes 35 | 36 | - Updated dependencies [33c8a81] 37 | - @supabase/auth-helpers-shared@0.3.0 38 | 39 | ## 0.1.4 40 | 41 | ### Patch Changes 42 | 43 | - d3366e4: Allow passing client options 44 | - Updated dependencies [d3366e4] 45 | - @supabase/auth-helpers-shared@0.2.4 46 | 47 | ## 0.1.3 48 | 49 | ### Patch Changes 50 | 51 | - Updated dependencies [bee77c7] 52 | - @supabase/auth-helpers-shared@0.2.3 53 | 54 | ## 0.1.2 55 | 56 | ### Patch Changes 57 | 58 | - d52d978: fix: export types. 59 | 60 | ## 0.1.1 61 | 62 | ### Patch Changes 63 | 64 | - Updated dependencies [999e57e] 65 | - @supabase/auth-helpers-shared@0.2.2 66 | 67 | ## 0.1.0 68 | 69 | ### Minor Changes 70 | 71 | - d0c8886: Add Remix Auth Helpers 72 | -------------------------------------------------------------------------------- /examples/sveltekit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@example/sveltekit", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev --port 3001", 7 | "build:example": "vite build", 8 | "preview": "vite preview", 9 | "check": "svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", 11 | "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", 12 | "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. .", 13 | "clean:all": "rimraf .svelte-kit node_modules" 14 | }, 15 | "devDependencies": { 16 | "@sveltejs/adapter-auto": "^1.0.0", 17 | "@sveltejs/kit": "^1.0.0", 18 | "@typescript-eslint/eslint-plugin": "^5.48.0", 19 | "@typescript-eslint/parser": "^5.48.0", 20 | "eslint": "^8.31.0", 21 | "eslint-config-prettier": "^8.6.0", 22 | "eslint-plugin-svelte3": "^4.0.0", 23 | "prettier": "^2.8.1", 24 | "prettier-plugin-svelte": "^2.9.0", 25 | "rimraf": "^3.0.2", 26 | "svelte": "^3.55.0", 27 | "svelte-check": "^3.0.1", 28 | "svelte-preprocess": "^5.0.0", 29 | "tsconfig": "workspace:*", 30 | "tslib": "^2.4.1", 31 | "typescript": "^4.9.4", 32 | "vite": "^4.0.4" 33 | }, 34 | "type": "module", 35 | "dependencies": { 36 | "@supabase/auth-helpers-sveltekit": "workspace:*", 37 | "@supabase/supabase-js": "^2.7.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import 'server-only'; 2 | 3 | import SupabaseListener from '../components/supabase-listener'; 4 | import SupabaseProvider from '../components/supabase-provider'; 5 | import Login from '../components/login'; 6 | import './globals.css'; 7 | import { createServerClient } from '../utils/supabase-server'; 8 | 9 | import type { Database } from '../db_types'; 10 | import type { SupabaseClient } from '@supabase/auth-helpers-nextjs'; 11 | 12 | export type TypedSupabaseClient = SupabaseClient; 13 | 14 | // do not cache this layout 15 | export const revalidate = 0; 16 | 17 | export default async function RootLayout({ 18 | children 19 | }: { 20 | children: React.ReactNode; 21 | }) { 22 | const supabase = createServerClient(); 23 | 24 | const { 25 | data: { session } 26 | } = await supabase.auth.getSession(); 27 | 28 | return ( 29 | 30 | {/* 31 | will contain the components returned by the nearest parent 32 | head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head 33 | */} 34 | 35 | 36 | 37 | 38 | 39 | {children} 40 | 41 | 42 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/components/login.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useSupabase } from './supabase-provider'; 4 | 5 | // Supabase auth needs to be triggered client-side 6 | export default function Login() { 7 | const { supabase, session } = useSupabase(); 8 | 9 | const handleEmailLogin = async () => { 10 | const { error } = await supabase.auth.signInWithPassword({ 11 | email: 'jon@supabase.com', 12 | password: 'password' 13 | }); 14 | 15 | if (error) { 16 | console.log({ error }); 17 | } 18 | }; 19 | 20 | const handleGitHubLogin = async () => { 21 | const { error } = await supabase.auth.signInWithOAuth({ 22 | provider: 'github' 23 | }); 24 | 25 | if (error) { 26 | console.log({ error }); 27 | } 28 | }; 29 | 30 | const handleLogout = async () => { 31 | const { error } = await supabase.auth.signOut(); 32 | 33 | if (error) { 34 | console.log({ error }); 35 | } 36 | }; 37 | 38 | // this `session` is from the root loader - server-side 39 | // therefore, it can safely be used to conditionally render 40 | // SSR pages without issues with hydration 41 | return session ? ( 42 | 43 | ) : ( 44 | <> 45 | 46 | 47 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@example/sveltekit-magic-link", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev --port 3003", 7 | "build:example": "vite build", 8 | "preview": "vite preview", 9 | "check": "svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", 11 | "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", 12 | "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. .", 13 | "clean:all": "rimraf .svelte-kit node_modules" 14 | }, 15 | "devDependencies": { 16 | "@sveltejs/adapter-auto": "^1.0.0", 17 | "@sveltejs/kit": "^1.0.0", 18 | "@typescript-eslint/eslint-plugin": "^5.48.0", 19 | "@typescript-eslint/parser": "^5.48.0", 20 | "eslint": "^8.31.0", 21 | "eslint-config-prettier": "^8.6.0", 22 | "eslint-plugin-svelte3": "^4.0.0", 23 | "prettier": "^2.8.1", 24 | "prettier-plugin-svelte": "^2.9.0", 25 | "rimraf": "^3.0.2", 26 | "svelte": "^3.55.0", 27 | "svelte-check": "^3.0.1", 28 | "svelte-preprocess": "^5.0.0", 29 | "tsconfig": "workspace:*", 30 | "tslib": "^2.4.1", 31 | "typescript": "^4.9.4", 32 | "vite": "^4.0.4" 33 | }, 34 | "type": "module", 35 | "dependencies": { 36 | "@supabase/auth-helpers-sveltekit": "workspace:*", 37 | "@supabase/supabase-js": "^2.7.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@example/sveltekit-email-password", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev --port 3002", 7 | "build:example": "vite build", 8 | "preview": "vite preview", 9 | "check": "svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", 11 | "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", 12 | "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. .", 13 | "clean:all": "rimraf .svelte-kit node_modules" 14 | }, 15 | "devDependencies": { 16 | "@sveltejs/adapter-auto": "^1.0.0", 17 | "@sveltejs/kit": "^1.0.0", 18 | "@typescript-eslint/eslint-plugin": "^5.48.0", 19 | "@typescript-eslint/parser": "^5.48.0", 20 | "eslint": "^8.31.0", 21 | "eslint-config-prettier": "^8.6.0", 22 | "eslint-plugin-svelte3": "^4.0.0", 23 | "prettier": "^2.8.1", 24 | "prettier-plugin-svelte": "^2.9.0", 25 | "rimraf": "^3.0.2", 26 | "svelte": "^3.55.0", 27 | "svelte-check": "^3.0.1", 28 | "svelte-preprocess": "^5.0.0", 29 | "tsconfig": "workspace:*", 30 | "tslib": "^2.4.1", 31 | "typescript": "^4.9.4", 32 | "vite": "^4.0.4" 33 | }, 34 | "type": "module", 35 | "dependencies": { 36 | "@supabase/auth-helpers-sveltekit": "workspace:*", 37 | "@supabase/supabase-js": "^2.7.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/sveltekit-magic-link/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 |
19 |
20 | {#if form?.error} 21 |
{form.error}
22 | {/if} 23 | {#if form?.message} 24 |
{form.message}
25 | {/if} 26 | 27 |
28 |
29 | 30 |

31 | 40 |

41 |
42 |
43 |

44 | 45 |

46 |
47 |
48 |
49 |
50 |
51 | -------------------------------------------------------------------------------- /packages/remix/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@supabase/auth-helpers-remix", 3 | "version": "0.1.9", 4 | "description": "A collection of framework specific Auth utilities for working with Supabase.", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "publishConfig": { 8 | "access": "public" 9 | }, 10 | "scripts": { 11 | "build": "tsup", 12 | "clean:all": "rimraf dist node_modules" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/supabase/auth-helpers.git" 17 | }, 18 | "keywords": [ 19 | "Supabase", 20 | "Auth", 21 | "Remix" 22 | ], 23 | "author": "Supabase", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/supabase/auth-helpers/issues" 27 | }, 28 | "homepage": "https://github.com/supabase/auth-helpers/tree/main/packages/remix#readme", 29 | "devDependencies": { 30 | "@remix-run/node": "^1.7.3", 31 | "@remix-run/react": "^1.7.3", 32 | "@remix-run/serve": "^1.7.3", 33 | "@supabase/supabase-js": "2.0.4", 34 | "config": "workspace:*", 35 | "next": "^12.2.5", 36 | "react": "^18.0.0", 37 | "react-dom": "^18.0.0", 38 | "rimraf": "^4.1.1", 39 | "tsconfig": "workspace:*", 40 | "tslib": "^2.4.1", 41 | "tsup": "^6.5.0" 42 | }, 43 | "dependencies": { 44 | "@supabase/auth-helpers-shared": "workspace:*" 45 | }, 46 | "peerDependencies": { 47 | "@supabase/supabase-js": "^2.0.4" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/remix/app/routes/__supabase/realtime.tsx: -------------------------------------------------------------------------------- 1 | import { Form, useLoaderData } from '@remix-run/react'; 2 | import { createServerClient } from 'utils/supabase.server'; 3 | import { json } from '@remix-run/node'; 4 | import RealtimePosts from 'components/realtime-posts'; 5 | 6 | import type { ActionArgs, LoaderArgs } from '@remix-run/node'; 7 | 8 | export const action = async ({ request }: ActionArgs) => { 9 | const response = new Response(); 10 | const supabase = createServerClient({ request, response }); 11 | 12 | const { post } = Object.fromEntries(await request.formData()); 13 | 14 | const { error } = await supabase 15 | .from('posts') 16 | .insert({ content: String(post) }); 17 | 18 | if (error) { 19 | console.log(error); 20 | } 21 | 22 | return json(null, { headers: response.headers }); 23 | }; 24 | 25 | export const loader = async ({ request }: LoaderArgs) => { 26 | const response = new Response(); 27 | const supabase = createServerClient({ request, response }); 28 | 29 | const { data } = await supabase.from('posts').select(); 30 | 31 | return json({ posts: data ?? [] }, { headers: response.headers }); 32 | }; 33 | 34 | export default function Index() { 35 | const { posts } = useLoaderData(); 36 | 37 | return ( 38 | <> 39 | 40 |
41 | 42 | 43 |
44 | 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/src/routes/(app)/signup/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { AuthApiError } from '@supabase/supabase-js'; 2 | import { fail, type ActionFailure } from '@sveltejs/kit'; 3 | import type { Actions } from './$types'; 4 | 5 | export const actions: Actions = { 6 | async default({ 7 | request, 8 | url, 9 | locals: { supabase } 10 | }): Promise | { message: string }> { 11 | const formData = await request.formData(); 12 | 13 | const email = formData.get('email') as string; 14 | const password = formData.get('password') as string; 15 | 16 | if (!email) { 17 | return fail(400, { 18 | error: 'Please enter your email' 19 | }); 20 | } 21 | if (!password) { 22 | return fail(400, { 23 | error: 'Please enter a password', 24 | values: { 25 | email 26 | } 27 | }); 28 | } 29 | 30 | const { error } = await supabase.auth.signUp({ 31 | email, 32 | password, 33 | options: { emailRedirectTo: url.origin } 34 | }); 35 | 36 | if (error) { 37 | if (error instanceof AuthApiError && error.status === 400) { 38 | return fail(400, { 39 | error: 'Invalid credentials.', 40 | values: { 41 | email 42 | } 43 | }); 44 | } 45 | 46 | return fail(500, { 47 | error: 'Server error. Try again later.', 48 | values: { 49 | email 50 | } 51 | }); 52 | } 53 | 54 | return { 55 | message: 'Please check your email for a magic link to log into the website.' 56 | }; 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /examples/nextjs/pages/protected-page.tsx: -------------------------------------------------------------------------------- 1 | // pages/protected-page.js 2 | import { 3 | createServerSupabaseClient, 4 | User 5 | } from '@supabase/auth-helpers-nextjs'; 6 | import { GetServerSidePropsContext } from 'next'; 7 | import Link from 'next/link'; 8 | 9 | export default function ProtectedPage({ 10 | user, 11 | data 12 | }: { 13 | user: User; 14 | data: any; 15 | }) { 16 | return ( 17 | <> 18 |

19 | [Home] | [ 20 | getServerSideProps] 21 |

22 |
Protected content for {user?.email}
23 |

server-side fetched data with RLS:

24 |
{JSON.stringify(data, null, 2)}
25 |

user:

26 |
{JSON.stringify(user, null, 2)}
27 | 28 | ); 29 | } 30 | 31 | export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { 32 | // Create authenticated Supabase Client 33 | const supabase = createServerSupabaseClient(ctx); 34 | // Check if we have a session 35 | const { 36 | data: { session } 37 | } = await supabase.auth.getSession(); 38 | 39 | if (!session) 40 | return { 41 | redirect: { 42 | destination: '/', 43 | permanent: false 44 | } 45 | }; 46 | 47 | // Run queries with RLS on the server 48 | const { data } = await supabase.from('users').select('*'); 49 | 50 | return { 51 | props: { 52 | initialSession: session, 53 | user: session.user, 54 | data: data ?? [] 55 | } 56 | }; 57 | }; 58 | -------------------------------------------------------------------------------- /examples/remix/app/routes/__supabase/required-session.tsx: -------------------------------------------------------------------------------- 1 | import { json, redirect } from '@remix-run/node'; 2 | import { useLoaderData } from '@remix-run/react'; 3 | import { createServerClient } from 'utils/supabase.server'; 4 | 5 | import type { LoaderArgs } from '@remix-run/node'; 6 | 7 | export const loader = async ({ request }: LoaderArgs) => { 8 | const response = new Response(); 9 | const supabase = createServerClient({ request, response }); 10 | 11 | const { 12 | data: { session } 13 | } = await supabase.auth.getSession(); 14 | 15 | if (!session) { 16 | // there is no session, therefore, we are redirecting 17 | // to the landing page. The `/?index` is required here 18 | // for Remix to correctly call our loaders 19 | return redirect('/?index', { 20 | // we still need to return response.headers to attach the set-cookie header 21 | headers: response.headers 22 | }); 23 | } 24 | 25 | const { data } = await supabase.from('posts').select('*'); 26 | 27 | // in order for the set-cookie header to be set, 28 | // headers must be returned as part of the loader response 29 | return json( 30 | { data, session }, 31 | { 32 | headers: response.headers 33 | } 34 | ); 35 | }; 36 | 37 | export default function RequiredSession() { 38 | // by fetching the session in the loader, we ensure it is available 39 | // for first SSR render - no flashing of incorrect state 40 | const { data, session } = useLoaderData(); 41 | 42 | return
{JSON.stringify({ data, session }, null, 2)}
; 43 | } 44 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/app/realtime/realtime-posts.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useEffect, useState } from 'react'; 4 | import { useSupabase } from '../../components/supabase-provider'; 5 | 6 | import type { Database } from '../../db_types'; 7 | 8 | type Post = Database['public']['Tables']['posts']['Row']; 9 | 10 | // realtime subscriptions need to be set up client-side 11 | // this component takes initial posts as props and automatically 12 | // updates when new posts are inserted into Supabase's `posts` table 13 | export default function RealtimePosts({ 14 | serverPosts 15 | }: { 16 | serverPosts: Post[]; 17 | }) { 18 | const [posts, setPosts] = useState(serverPosts); 19 | const { supabase } = useSupabase(); 20 | 21 | useEffect(() => { 22 | // this overwrites `posts` any time the `serverPosts` prop changes 23 | // this happens when the parent Server Component is re-rendered 24 | setPosts(serverPosts); 25 | }, [serverPosts]); 26 | 27 | useEffect(() => { 28 | // ensure you have enabled replication on the `posts` table 29 | // https://app.supabase.com/project/_/database/replication 30 | const channel = supabase 31 | .channel('*') 32 | .on( 33 | 'postgres_changes', 34 | { event: 'INSERT', schema: 'public', table: 'posts' }, 35 | (payload) => setPosts([...posts, payload.new as Post]) 36 | ) 37 | .subscribe(); 38 | 39 | return () => { 40 | supabase.removeChannel(channel); 41 | }; 42 | }, [supabase, setPosts, posts]); 43 | 44 | return
{JSON.stringify(posts, null, 2)}
; 45 | } 46 | -------------------------------------------------------------------------------- /packages/sveltekit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@supabase/auth-helpers-sveltekit", 3 | "version": "0.9.4", 4 | "description": "A collection of framework specific Auth utilities for working with Supabase.", 5 | "type": "module", 6 | "main": "./dist/index.js", 7 | "module": "./dist/index.js", 8 | "types": "./dist/index.d.ts", 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "exports": { 13 | "./package.json": "./package.json", 14 | ".": "./dist/index.js" 15 | }, 16 | "files": [ 17 | "dist" 18 | ], 19 | "scripts": { 20 | "build": "tsup", 21 | "clean:all": "rimraf dist node_modules" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/supabase/auth-helpers.git" 26 | }, 27 | "keywords": [ 28 | "Supabase", 29 | "Auth", 30 | "Svelte Kit", 31 | "Svelte" 32 | ], 33 | "author": "Supabase", 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/supabase/auth-helpers/issues" 37 | }, 38 | "homepage": "https://github.com/supabase/auth-helpers/tree/main/packages/sveltekit#readme", 39 | "devDependencies": { 40 | "@supabase/supabase-js": "2.15.0", 41 | "@sveltejs/kit": "1.15.2", 42 | "svelte": "^3.54.0", 43 | "vite": "^4.0.0", 44 | "rimraf": "^4.1.1", 45 | "tslib": "^2.4.0", 46 | "typescript": "^4.7.4", 47 | "tsup": "^6.5.0", 48 | "tsconfig": "workspace:*" 49 | }, 50 | "dependencies": { 51 | "@supabase/auth-helpers-shared": "workspace:*" 52 | }, 53 | "peerDependencies": { 54 | "@sveltejs/kit": "^1.15.2", 55 | "@supabase/supabase-js": "^2.15.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/nextjs/db_types.ts: -------------------------------------------------------------------------------- 1 | export type Json = 2 | | string 3 | | number 4 | | boolean 5 | | null 6 | | { [key: string]: Json } 7 | | Json[]; 8 | 9 | export interface Database { 10 | public: { 11 | Tables: { 12 | test: { 13 | Row: { 14 | created_at: string | null; 15 | id: number; 16 | }; 17 | Insert: { 18 | created_at?: string | null; 19 | id?: number; 20 | }; 21 | Update: { 22 | created_at?: string | null; 23 | id?: number; 24 | }; 25 | }; 26 | users: { 27 | Row: { 28 | city: string | null; 29 | country: string | null; 30 | created_at: string; 31 | full_name: string | null; 32 | id: string; 33 | username: string | null; 34 | }; 35 | Insert: { 36 | city?: string | null; 37 | country?: string | null; 38 | created_at?: string; 39 | full_name?: string | null; 40 | id: string; 41 | username?: string | null; 42 | }; 43 | Update: { 44 | city?: string | null; 45 | country?: string | null; 46 | created_at?: string; 47 | full_name?: string | null; 48 | id?: string; 49 | username?: string | null; 50 | }; 51 | }; 52 | }; 53 | Views: { 54 | [_ in never]: never; 55 | }; 56 | Functions: { 57 | [_ in never]: never; 58 | }; 59 | Enums: { 60 | [_ in never]: never; 61 | }; 62 | CompositeTypes: { 63 | [_ in never]: never; 64 | }; 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /examples/sveltekit/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 |

Welcome to SvelteKit

23 |

Visit kit.svelte.dev to read the documentation

24 | 25 | {#if !$page.data.session} 26 | 36 | 46 | {:else} 47 |

48 | [withPageAuth] | [supabaseServerClient] | [GitHub Token] | 51 | 54 |

55 | 56 |

user:

57 |
{JSON.stringify($page.data.session.user, null, 2)}
58 |

client-side data fetching with RLS

59 |
{JSON.stringify(loadedData, null, 2)}
60 | {/if} 61 | -------------------------------------------------------------------------------- /examples/nextjs/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 16 | 17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 18 | 19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 16 | 17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 18 | 19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /packages/sveltekit/src/loadStorageAdapter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CookieOptions, 3 | isBrowser, 4 | parseCookies, 5 | parseSupabaseCookie, 6 | serializeCookie, 7 | stringifySupabaseSession 8 | } from '@supabase/auth-helpers-shared'; 9 | import { Session, GoTrueClientOptions } from '@supabase/supabase-js'; 10 | 11 | export function supabaseAuthStorageAdapterSveltekitLoad({ 12 | serverSession, 13 | cookieOptions: { 14 | name = 'sb-auth-token', 15 | domain, 16 | maxAge = 60 * 60 * 24 * 365, 17 | path = '/', 18 | sameSite, 19 | secure 20 | } = {} 21 | }: { 22 | serverSession?: Session | null; 23 | cookieOptions?: CookieOptions; 24 | }): GoTrueClientOptions['storage'] { 25 | if (!isBrowser()) { 26 | return { 27 | async getItem() { 28 | return JSON.stringify(serverSession); 29 | }, 30 | setItem() {}, 31 | removeItem() {} 32 | }; 33 | } 34 | 35 | return { 36 | async getItem() { 37 | const sessionStr = parseCookies(document.cookie)[name]; 38 | const session = parseSupabaseCookie(sessionStr); 39 | return JSON.stringify(session); 40 | }, 41 | async setItem(_key: string, value: string) { 42 | const session = JSON.parse(value); 43 | const sessionStr = stringifySupabaseSession(session); 44 | document.cookie = serializeCookie(name, sessionStr, { 45 | domain, 46 | maxAge, 47 | path, 48 | sameSite, 49 | secure, 50 | httpOnly: false 51 | }); 52 | }, 53 | async removeItem() { 54 | document.cookie = serializeCookie(name, '', { 55 | domain, 56 | maxAge: 0, 57 | path, 58 | sameSite, 59 | secure, 60 | httpOnly: false 61 | }); 62 | } 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /examples/nextjs/pages/github-provider-token.tsx: -------------------------------------------------------------------------------- 1 | // pages/protected-page.js 2 | import { 3 | User, 4 | createServerSupabaseClient 5 | } from '@supabase/auth-helpers-nextjs'; 6 | import { GetServerSidePropsContext } from 'next'; 7 | import Link from 'next/link'; 8 | 9 | export default function ProtectedPage({ 10 | user, 11 | allRepos 12 | }: { 13 | user: User; 14 | allRepos: any; 15 | }) { 16 | return ( 17 | <> 18 |

19 | [Home] | [ 20 | withPageAuth] 21 |

22 |
Protected content for {user.email}
23 |

Data fetched with provider token:

24 |
{JSON.stringify(allRepos, null, 2)}
25 |

user:

26 |
{JSON.stringify(user, null, 2)}
27 | 28 | ); 29 | } 30 | 31 | export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { 32 | // Create authenticated Supabase Client 33 | const supabase = createServerSupabaseClient(ctx); 34 | // Check if we have a session 35 | const { 36 | data: { session } 37 | } = await supabase.auth.getSession(); 38 | 39 | if (!session) 40 | return { 41 | redirect: { 42 | destination: '/', 43 | permanent: false 44 | } 45 | }; 46 | 47 | // Retrieve provider_token & logged in user's third-party id from metadata 48 | const { provider_token, user } = session; 49 | const userId = user.user_metadata.user_name; 50 | 51 | const allRepos = await ( 52 | await fetch(`https://api.github.com/search/repositories?q=user:${userId}`, { 53 | method: 'GET', 54 | headers: { 55 | Authorization: `token ${provider_token}` 56 | } 57 | }) 58 | ).json(); 59 | 60 | return { props: { user, allRepos } }; 61 | }; 62 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/app/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import styles from './page.module.css'; 3 | 4 | export default function Home() { 5 | return ( 6 | 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/src/routes/(app)/signup/+page.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 |
18 |
19 |

Sign up

20 | {#if form?.error} 21 |
{form.error}
22 | {/if} 23 | {#if form?.message} 24 |
{form.message}
25 | {/if} 26 |
27 |
28 | 29 |

30 | 39 |

40 |
41 |
42 | 43 |

44 | 52 |

53 |
54 |
55 |

56 | 57 |

58 |
59 |
60 | 61 |
62 |

63 | Already have an account? Sign in 64 |

65 |
66 |
67 |
68 | -------------------------------------------------------------------------------- /examples/sveltekit-email-password/src/routes/(app)/+page.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 |
23 |
24 |

Sign in

25 | {#if form?.error} 26 |
{form.error}
27 | {/if} 28 |
29 |
30 | 31 |

32 | 41 |

42 |
43 |
44 | 45 |

46 | 54 |

55 |
56 |
57 |

58 | 59 |

60 |
61 |
62 | 63 |
64 |

65 | Don't have an account? Sign up 66 |

67 |
68 |
69 |
70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@supabase/auth-helpers", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "turbo run build", 7 | "build:example": "turbo run build:example", 8 | "build:example:sveltekit": "turbo run build:example --filter=@example/sveltekit", 9 | "build:example:nextjs": "turbo run build:example --filter=@example/nextjs", 10 | "build:sveltekit": "turbo run build --filter=@supabase/auth-helpers-sveltekit", 11 | "build:sveltekit:watch": "turbo run build:watch --filter=@supabase/auth-helpers-sveltekit", 12 | "build:nextjs": "turbo run build --filter=@supabase/auth-helpers-nextjs", 13 | "build:react": "turbo run build --filter=@supabase/auth-helpers-react", 14 | "build:remix": "turbo run build --filter=@supabase/auth-helpers-remix", 15 | "build:shared": "turbo run build --filter=@supabase/auth-helpers-shared", 16 | "dev": "turbo run dev --parallel", 17 | "lint": "turbo run lint", 18 | "format": "prettier --write \"**/*.{ts,tsx,md}\" --ignore-path .gitignore", 19 | "docs": "typedoc", 20 | "clean:all": "turbo run clean:all", 21 | "ci:version": "changeset version", 22 | "ci:release": "changeset publish" 23 | }, 24 | "devDependencies": { 25 | "@changesets/cli": "^2.22.0", 26 | "prettier": "^2.5.1", 27 | "turbo": "^1.8.3", 28 | "typedoc": "^0.22.17", 29 | "typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x || 4.6.x" 30 | }, 31 | "engines": { 32 | "npm": ">=7.0.0", 33 | "node": ">=16.0.0" 34 | }, 35 | "packageManager": "pnpm@7.1.7", 36 | "peerDependencies": { 37 | "typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x || 4.6.x" 38 | }, 39 | "pnpm": { 40 | "overrides": { 41 | "@changesets/assemble-release-plan": "5.2.3" 42 | }, 43 | "overrides-notes": { 44 | "@changesets/assemble-release-plan": "patched until https://github.com/changesets/changesets/issues/835 is resolved" 45 | }, 46 | "patchedDependencies": { 47 | "@changesets/assemble-release-plan@5.2.3": "patches/@changesets__assemble-release-plan@5.2.3.patch" 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/shared/src/utils/errors.ts: -------------------------------------------------------------------------------- 1 | export interface ErrorPayload { 2 | type?: string; 3 | message: string; 4 | source?: string; 5 | } 6 | 7 | export class AuthHelperError extends Error { 8 | errorType: string; 9 | source: string; 10 | 11 | constructor(message: string, errorType: string) { 12 | super(message); 13 | this.errorType = errorType; 14 | this.source = 'sb_auth_helpers'; 15 | } 16 | 17 | toObj(): ErrorPayload { 18 | return { 19 | type: this.errorType, 20 | message: this.message, 21 | source: this.source 22 | }; 23 | } 24 | 25 | toString() { 26 | return JSON.stringify(this.toObj()); 27 | } 28 | } 29 | 30 | export class CookieNotFound extends AuthHelperError { 31 | constructor() { 32 | super('No cookie was found!', 'cookie_not_found'); 33 | } 34 | } 35 | 36 | export class CookieNotSaved extends AuthHelperError { 37 | constructor() { 38 | super('Cookies cannot be saved!', 'cookie_not_saved'); 39 | } 40 | } 41 | 42 | export class AccessTokenNotFound extends AuthHelperError { 43 | constructor() { 44 | super('No access token was found!', 'cookie_not_found'); 45 | } 46 | } 47 | 48 | export class RefreshTokenNotFound extends AuthHelperError { 49 | constructor() { 50 | super('No refresh token was found!', 'cookie_not_found'); 51 | } 52 | } 53 | 54 | export class ProviderTokenNotFound extends AuthHelperError { 55 | constructor() { 56 | super('No provider token was found!', 'cookie_not_found'); 57 | } 58 | } 59 | 60 | export class CookieNotParsed extends AuthHelperError { 61 | constructor() { 62 | super('Not able to parse cookies!', 'cookie_not_parsed'); 63 | } 64 | } 65 | 66 | export class CallbackUrlFailed extends AuthHelperError { 67 | constructor(callbackUrl: string) { 68 | super(`The request to ${callbackUrl} failed!`, 'callback_url_failed'); 69 | } 70 | } 71 | 72 | export class JWTPayloadFailed extends AuthHelperError { 73 | constructor() { 74 | super('Not able to parse JWT payload!', 'jwt_payload_failed'); 75 | } 76 | } 77 | 78 | export class JWTInvalid extends AuthHelperError { 79 | constructor() { 80 | super('Invalid jwt!', 'jwt_invalid'); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/sveltekit/src/serverStorageAdapter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CookieOptions, 3 | parseSupabaseCookie, 4 | stringifySupabaseSession 5 | } from '@supabase/auth-helpers-shared'; 6 | import { RequestEvent } from '@sveltejs/kit'; 7 | import { GoTrueClientOptions, Session } from '@supabase/supabase-js'; 8 | 9 | export function supabaseAuthStorageAdapterSveltekitServer({ 10 | cookies, 11 | cookieOptions: { 12 | name = 'sb-auth-token', 13 | domain, 14 | maxAge = 60 * 60 * 24 * 365, 15 | path = '/', 16 | sameSite, 17 | secure, 18 | httpOnly = false 19 | } = {}, 20 | expiryMargin = 60 21 | }: { 22 | cookies: RequestEvent['cookies']; 23 | cookieOptions?: CookieOptions & { httpOnly?: boolean }; 24 | expiryMargin?: number; 25 | }): GoTrueClientOptions['storage'] { 26 | let currentSession: Partial | null; 27 | let isInitialDelete = true; 28 | 29 | return { 30 | async getItem() { 31 | const sessionStr = cookies.get(name); 32 | const session = (currentSession = parseSupabaseCookie(sessionStr)); 33 | if (session?.expires_at) { 34 | // shorten the session lifetime so it does not expire on the server 35 | session.expires_at -= expiryMargin; 36 | } 37 | return JSON.stringify(session); 38 | }, 39 | async setItem(_key: string, value: string) { 40 | const session = JSON.parse(value); 41 | const sessionStr = stringifySupabaseSession(session); 42 | cookies.set(name, sessionStr, { 43 | domain, 44 | maxAge, 45 | path, 46 | sameSite, 47 | secure, 48 | httpOnly 49 | }); 50 | }, 51 | async removeItem() { 52 | // workaround until https://github.com/supabase/gotrue-js/pull/598 53 | if (isInitialDelete && currentSession?.expires_at) { 54 | const now = Math.round(Date.now() / 1000); 55 | if (currentSession.expires_at < now + 10) { 56 | isInitialDelete = false; 57 | return; 58 | } 59 | } 60 | cookies.delete(name, { 61 | domain, 62 | maxAge, 63 | path, 64 | sameSite, 65 | secure, 66 | httpOnly 67 | }); 68 | } 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /patches/@changesets__assemble-release-plan@5.2.3.patch: -------------------------------------------------------------------------------- 1 | diff --git a/dist/assemble-release-plan.cjs.dev.js b/dist/assemble-release-plan.cjs.dev.js 2 | index 3a37c62c975518f975c22e1b4b3974d9b325a5da..4aaad4630b4d3cf31738b105d8a1e42a428cee2c 100644 3 | --- a/dist/assemble-release-plan.cjs.dev.js 4 | +++ b/dist/assemble-release-plan.cjs.dev.js 5 | @@ -430,7 +430,7 @@ function applyLinks(releases, packagesByName, linked) { 6 | 7 | function getPreVersion(version) { 8 | let parsed = semver.parse(version); 9 | - let preVersion = parsed.prerelease[1] === undefined ? -1 : parsed.prerelease[1]; 10 | + let preVersion = parsed?.prerelease[1] === undefined ? -1 : parsed.prerelease[1]; 11 | 12 | if (typeof preVersion !== "number") { 13 | throw new errors.InternalError("preVersion is not a number"); 14 | diff --git a/dist/assemble-release-plan.cjs.prod.js b/dist/assemble-release-plan.cjs.prod.js 15 | index 87b4c104bf3fa53ba498ced6f81eda0ed4c86436..bbd6f6a9dadef83b1bb5b95e6883b2fd235fe653 100644 16 | --- a/dist/assemble-release-plan.cjs.prod.js 17 | +++ b/dist/assemble-release-plan.cjs.prod.js 18 | @@ -214,7 +214,7 @@ function applyLinks(releases, packagesByName, linked) { 19 | } 20 | 21 | function getPreVersion(version) { 22 | - let parsed = semver.parse(version), preVersion = void 0 === parsed.prerelease[1] ? -1 : parsed.prerelease[1]; 23 | + let parsed = semver.parse(version), preVersion = void 0 === parsed?.prerelease[1] ? -1 : parsed.prerelease[1]; 24 | if ("number" != typeof preVersion) throw new errors.InternalError("preVersion is not a number"); 25 | return preVersion++, preVersion; 26 | } 27 | diff --git a/src/index.ts b/src/index.ts 28 | index 3ffb6fa772b78506bd7de7a4fcb41c004733b00d..54c64132371059e535d9fe2f9b946f1c5d2cee45 100644 29 | --- a/src/index.ts 30 | +++ b/src/index.ts 31 | @@ -24,7 +24,7 @@ type SnapshotReleaseParameters = { 32 | function getPreVersion(version: string) { 33 | let parsed = semver.parse(version)!; 34 | let preVersion = 35 | - parsed.prerelease[1] === undefined ? -1 : parsed.prerelease[1]; 36 | + parsed?.prerelease[1] === undefined ? -1 : parsed.prerelease[1]; 37 | if (typeof preVersion !== "number") { 38 | throw new InternalError("preVersion is not a number"); 39 | } -------------------------------------------------------------------------------- /development.md: -------------------------------------------------------------------------------- 1 | This repository is a monorepo and makes use of [Turborepo](https://turborepo.org/) and PNPM workspaces. 2 | 3 | ## Set up 4 | 5 | Before you begin, make sure you have the following set up on your local machine. 6 | 7 | - Install [NodeJS v16.x (LTS)](https://nodejs.org/en/) 8 | - Install [PNPM](https://pnpm.io/installation) 9 | 10 | > All commands below should be run at the root level of the cloned repository. 11 | 12 | ### Install package dependencies 13 | 14 | ```bash 15 | pnpm install 16 | ``` 17 | 18 | ## Build 19 | 20 | You can build all packages using the following command: 21 | 22 | ```bash 23 | pnpm build 24 | ``` 25 | 26 | Or you can run build for the individual packages using the following command: 27 | 28 | ```bash 29 | pnpm build:[dirname] 30 | ``` 31 | 32 | For react it would be 33 | 34 | ```bash 35 | pnpm build:react 36 | ``` 37 | 38 | ## Local development and testing 39 | 40 | Once built, you can run all the packages and examples in development mode using the following command: 41 | 42 | ```bash 43 | pnpm dev 44 | ``` 45 | 46 | After making and building your changes, make sure that the examples continue working and, if needed, update the relevant examples to test changes or added functionality. 47 | 48 | ## Preparing a release 49 | 50 | **NOTE:** Do not touch the version number of any of the packages. These are automatically updated in CI via changeset! 51 | 52 | --- 53 | 54 | Once you made your changes, built and tested them, you will need to generate a changelog entry via running: 55 | 56 | ```sh 57 | pnpm changeset 58 | ``` 59 | 60 | Step through the steps, select the packages that you've made changes to and indicate what kind of release this is. 61 | 62 | Changeset will generate a temporary file (see [example](https://github.com/supabase/auth-helpers/commit/63b1da08ec7b26ff2fe87b4d3c6e0e5f24fc1dc6#diff-bac6aefca6cf9c72965d3202f7d19999562965eb8831fce29cf2e3e0f6bcdc33)). Make sure to commit this file to your PR as this is used in CI to generate the release. 63 | 64 | Once your PR is merged, CI will generate a PR (see [example](https://github.com/supabase/auth-helpers/commit/2cda81bda4ba810b9e73baaca238facc51bab2ce)) with the changelog entries, increment the version numbers accordingly, and remove the temporary changeset files. Upon merging this PR, CI will issue the new release to npm. 🥳 65 | -------------------------------------------------------------------------------- /examples/nextjs/styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 2rem; 3 | } 4 | 5 | .main { 6 | min-height: 100vh; 7 | padding: 4rem 0; 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .footer { 16 | display: flex; 17 | flex: 1; 18 | padding: 2rem 0; 19 | border-top: 1px solid #eaeaea; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .footer a { 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | flex-grow: 1; 29 | } 30 | 31 | .title a { 32 | color: #0070f3; 33 | text-decoration: none; 34 | } 35 | 36 | .title a:hover, 37 | .title a:focus, 38 | .title a:active { 39 | text-decoration: underline; 40 | } 41 | 42 | .title { 43 | margin: 0; 44 | line-height: 1.15; 45 | font-size: 4rem; 46 | } 47 | 48 | .title, 49 | .description { 50 | text-align: center; 51 | } 52 | 53 | .description { 54 | margin: 4rem 0; 55 | line-height: 1.5; 56 | font-size: 1.5rem; 57 | } 58 | 59 | .code { 60 | background: #fafafa; 61 | border-radius: 5px; 62 | padding: 0.75rem; 63 | font-size: 1.1rem; 64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 65 | Bitstream Vera Sans Mono, Courier New, monospace; 66 | } 67 | 68 | .grid { 69 | display: flex; 70 | align-items: center; 71 | justify-content: center; 72 | flex-wrap: wrap; 73 | max-width: 800px; 74 | } 75 | 76 | .card { 77 | margin: 1rem; 78 | padding: 1.5rem; 79 | text-align: left; 80 | color: inherit; 81 | text-decoration: none; 82 | border: 1px solid #eaeaea; 83 | border-radius: 10px; 84 | transition: color 0.15s ease, border-color 0.15s ease; 85 | max-width: 300px; 86 | } 87 | 88 | .card:hover, 89 | .card:focus, 90 | .card:active { 91 | color: #0070f3; 92 | border-color: #0070f3; 93 | } 94 | 95 | .card h2 { 96 | margin: 0 0 1rem 0; 97 | font-size: 1.5rem; 98 | } 99 | 100 | .card p { 101 | margin: 0; 102 | font-size: 1.25rem; 103 | line-height: 1.5; 104 | } 105 | 106 | .logo { 107 | height: 1em; 108 | margin-left: 0.5rem; 109 | } 110 | 111 | @media (max-width: 600px) { 112 | .grid { 113 | width: 100%; 114 | flex-direction: column; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @supabase/auth-helpers (BETA) 2 | 3 | A collection of framework specific Auth utilities for working with Supabase. 4 | 5 | ## Supported Frameworks 6 | 7 | - [Next.js](https://nextjs.org) [[Documentation](https://supabase.com/docs/guides/auth/auth-helpers/nextjs)] 8 | - [Nuxt - via @nuxtjs/supabase](https://supabase.nuxtjs.org/) 9 | - [SvelteKit](https://kit.svelte.dev) [[Documentation](https://supabase.com/docs/guides/auth/auth-helpers/sveltekit)] 10 | - [Remix](https://remix.run/) [[Documentation](https://supabase.com/docs/guides/auth/auth-helpers/remix)] 11 | 12 | ### Examples and Packages 13 | 14 | - Examples 15 | - `@examples/nextjs`: a [Next.js](https://nextjs.org) app 16 | - `@examples/nextjs-server-components`: a [Next.js](https://nextjs.org) 13 app with Server Components and `app` directory 17 | - `@examples/sveltekit`: a [SvelteKit](https://kit.svelte.dev) app 18 | - `@examples/sveltekit-email-password`: a [SvelteKit](https://kit.svelte.dev) app with SSR sign in 19 | - `@examples/sveltekit-magic-link`: a [SvelteKit](https://kit.svelte.dev) app with magic links 20 | - `@examples/remix`: a [Remix](https://remix.run/) app 21 | - Packages 22 | - `@supabase/auth-helpers-nextjs`: the supabase auth helper nextjs library used by `nextjs` application 23 | - `@supabase/auth-helpers-react`: the supabase auth helper reactjs library used by `react` application 24 | - `@supabase/auth-helpers-sveltekit`: the supabase auth helper sveltekit library used by `sveltekit` application 25 | - `@supabase/auth-helpers-remix`: the supabase auth helper remix library used by `remix` application 26 | - `shared`: shared typescript types used by `@supabase/auth-helpers-nextjs` library 27 | - `config`: `eslint` configurations (includes `eslint-config-next` and `eslint-config-prettier`) 28 | - `tsconfig`: `tsconfig.json`s used throughout the monorepo 29 | 30 | Each package/app is 100% [TypeScript](https://www.typescriptlang.org/). 31 | 32 | ### Utilities 33 | 34 | This turborepo has some additional tools already setup for you: 35 | 36 | - [TypeScript](https://www.typescriptlang.org/) for static type checking 37 | - [ESLint](https://eslint.org/) for code linting 38 | - [Prettier](https://prettier.io) for code formatting 39 | 40 | ## Development & Contributing 41 | 42 | Read the [development.md](./development.md) guide for more information on local setup, testing, and preparaing a release. 43 | 44 | Using a `@supabase/auth-helpers-[framework-name]` naming convention for packages 45 | -------------------------------------------------------------------------------- /packages/react/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @supabase/auth-helpers-react 2 | 3 | ## 0.3.1 4 | 5 | ### Patch Changes 6 | 7 | - 2fda843: add missing supabase-js peerDependency 8 | - 2fda843: remove auth-helpers-shared dependency in react package 9 | - 2fda843: update supabase-js 10 | 11 | ## 0.3.0 12 | 13 | ### Minor Changes 14 | 15 | - fd30e33: Update to work with supabase-js v2 RC 16 | 17 | ### Patch Changes 18 | 19 | - 3154f16: fix: typing for useSupabaseClient. 20 | - fe5c4a6: chore: improve types. 21 | - 2fdb094: chore: types and middleware improvements. 22 | - Updated dependencies [20fa944] 23 | - Updated dependencies [fd30e33] 24 | - Updated dependencies [fe5c4a6] 25 | - Updated dependencies [2fdb094] 26 | - @supabase/auth-helpers-shared@0.2.0 27 | 28 | ## 0.3.0-next.4 29 | 30 | ### Patch Changes 31 | 32 | - Updated dependencies [20fa944] 33 | - @supabase/auth-helpers-shared@0.2.0-next.3 34 | 35 | ## 0.3.0-next.3 36 | 37 | ### Patch Changes 38 | 39 | - 3154f16: fix: typing for useSupabaseClient. 40 | 41 | ## 0.3.0-next.2 42 | 43 | ### Patch Changes 44 | 45 | - 2fdb094: chore: types and middleware improvements. 46 | - Updated dependencies [2fdb094] 47 | - @supabase/auth-helpers-shared@0.2.0-next.2 48 | 49 | ## 0.3.0-next.1 50 | 51 | ### Patch Changes 52 | 53 | - fe5c4a6: chore: improve types. 54 | - Updated dependencies [fe5c4a6] 55 | - @supabase/auth-helpers-shared@0.2.0-next.1 56 | 57 | ## 0.3.0-next.0 58 | 59 | ### Minor Changes 60 | 61 | - 1b33e44: Update to work with supabase-js v2 RC 62 | 63 | ### Patch Changes 64 | 65 | - Updated dependencies [1b33e44] 66 | - @supabase/auth-helpers-shared@0.2.0-next.0 67 | 68 | ## 0.2.3 69 | 70 | ### Patch Changes 71 | 72 | - Updated dependencies [56228e3] 73 | - @supabase/auth-helpers-shared@0.1.3 74 | 75 | ## 0.2.2 76 | 77 | ### Patch Changes 78 | 79 | - Updated dependencies [38ccf1c] 80 | - @supabase/auth-helpers-shared@0.1.2 81 | 82 | ## 0.2.1 83 | 84 | ### Patch Changes 85 | 86 | - 9dda264: Add better error handling and error codes 87 | - 9dda264: Fix no cookie found issue 88 | - Updated dependencies [9dda264] 89 | - @supabase/auth-helpers-shared@0.1.1 90 | 91 | ## 0.2.0 92 | 93 | ### Minor Changes 94 | 95 | - f399820: Using shared package as a dependency 96 | Update sveltekit package with latest code to update tokens 97 | 98 | ## 0.1.0 99 | 100 | ### Minor Changes 101 | 102 | - a3c2991: Initial release of new library version 103 | -------------------------------------------------------------------------------- /packages/shared/src/supabase-browser.ts: -------------------------------------------------------------------------------- 1 | import { createClient, Session } from '@supabase/supabase-js'; 2 | import { parse, serialize } from 'cookie'; 3 | import { CookieOptions, SupabaseClientOptionsWithoutAuth } from './types'; 4 | import { parseSupabaseCookie, stringifySupabaseSession } from './utils/cookies'; 5 | import { isBrowser } from './utils/helpers'; 6 | 7 | export function createBrowserSupabaseClient< 8 | Database = any, 9 | SchemaName extends string & keyof Database = 'public' extends keyof Database 10 | ? 'public' 11 | : string & keyof Database 12 | >({ 13 | supabaseUrl, 14 | supabaseKey, 15 | options, 16 | cookieOptions: { 17 | name = 'supabase-auth-token', 18 | domain, 19 | path = '/', 20 | sameSite = 'lax', 21 | secure, 22 | maxAge = 1000 * 60 * 60 * 24 * 365 23 | } = {} 24 | }: { 25 | supabaseUrl: string; 26 | supabaseKey: string; 27 | options?: SupabaseClientOptionsWithoutAuth; 28 | cookieOptions?: CookieOptions; 29 | }) { 30 | return createClient(supabaseUrl, supabaseKey, { 31 | ...options, 32 | auth: { 33 | storageKey: name, 34 | storage: { 35 | getItem(key: string) { 36 | if (!isBrowser()) { 37 | return null; 38 | } 39 | 40 | const cookies = parse(document.cookie); 41 | const session = parseSupabaseCookie(cookies[key]); 42 | 43 | return session ? JSON.stringify(session) : null; 44 | }, 45 | setItem(key: string, _value: string) { 46 | if (!isBrowser()) { 47 | return; 48 | } 49 | 50 | let session: Session = JSON.parse(_value); 51 | const value = stringifySupabaseSession(session); 52 | 53 | document.cookie = serialize(key, value, { 54 | domain, 55 | path, 56 | maxAge, 57 | // Allow supabase-js on the client to read the cookie as well 58 | httpOnly: false, 59 | sameSite, 60 | secure: secure ?? document.location?.protocol === 'https:' 61 | }); 62 | }, 63 | removeItem(key: string) { 64 | if (!isBrowser()) { 65 | return; 66 | } 67 | 68 | document.cookie = serialize(key, '', { 69 | domain, 70 | path, 71 | expires: new Date(0), 72 | httpOnly: false, 73 | sameSite, 74 | secure 75 | }); 76 | } 77 | } 78 | } 79 | }); 80 | } 81 | -------------------------------------------------------------------------------- /examples/nextjs/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | useSessionContext, 3 | useSupabaseClient, 4 | useUser 5 | } from '@supabase/auth-helpers-react'; 6 | import { Auth, ThemeSupa } from '@supabase/auth-ui-react'; 7 | import type { NextPage } from 'next'; 8 | import Link from 'next/link'; 9 | import { useEffect, useState } from 'react'; 10 | import { Database } from '../db_types'; 11 | 12 | const LoginPage: NextPage = () => { 13 | const { isLoading, session, error } = useSessionContext(); 14 | const user = useUser(); 15 | const supabaseClient = useSupabaseClient(); 16 | 17 | const [data, setData] = useState(null); 18 | 19 | useEffect(() => { 20 | async function loadData() { 21 | const { data } = await supabaseClient.from('users').select('*').single(); 22 | setData(data); 23 | } 24 | 25 | if (user) loadData(); 26 | }, [user, supabaseClient]); 27 | 28 | if (!session) 29 | return ( 30 | <> 31 | {error &&

{error.message}

} 32 | {isLoading ?

Loading...

:

Loaded!

} 33 | 43 | 52 | 53 | ); 54 | 55 | return ( 56 | <> 57 |

58 | [getServerSideProps] | [ 59 | server-side RLS] |{' '} 60 | 67 | 70 |

71 | {isLoading ?

Loading...

:

Loaded!

} 72 |

user:

73 |
{JSON.stringify(session, null, 2)}
74 |

client-side data fetching with RLS

75 |
{JSON.stringify(data, null, 2)}
76 | 77 | ); 78 | }; 79 | 80 | export default LoginPage; 81 | -------------------------------------------------------------------------------- /packages/shared/src/supabase-server.ts: -------------------------------------------------------------------------------- 1 | import { createClient, Session } from '@supabase/supabase-js'; 2 | import type { CookieSerializeOptions } from 'cookie'; 3 | import { CookieOptions, SupabaseClientOptionsWithoutAuth } from './types'; 4 | import { 5 | isSecureEnvironment, 6 | parseSupabaseCookie, 7 | stringifySupabaseSession 8 | } from './utils/cookies'; 9 | 10 | export function createServerSupabaseClient< 11 | Database = any, 12 | SchemaName extends string & keyof Database = 'public' extends keyof Database 13 | ? 'public' 14 | : string & keyof Database 15 | >({ 16 | supabaseUrl, 17 | supabaseKey, 18 | getCookie, 19 | setCookie, 20 | getRequestHeader, 21 | options, 22 | cookieOptions: { 23 | name = 'supabase-auth-token', 24 | domain, 25 | path = '/', 26 | sameSite = 'lax', 27 | secure, 28 | maxAge = 1000 * 60 * 60 * 24 * 365 29 | } = {} 30 | }: { 31 | supabaseUrl: string; 32 | supabaseKey: string; 33 | getCookie: (name: string) => string | undefined; 34 | setCookie: ( 35 | name: string, 36 | value: string, 37 | options: CookieSerializeOptions 38 | ) => void; 39 | getRequestHeader: (name: string) => string | string[] | undefined; 40 | options?: SupabaseClientOptionsWithoutAuth; 41 | cookieOptions?: CookieOptions; 42 | }) { 43 | let currentSession = parseSupabaseCookie(getCookie(name)) ?? null; 44 | 45 | return createClient(supabaseUrl, supabaseKey, { 46 | ...options, 47 | auth: { 48 | detectSessionInUrl: false, 49 | autoRefreshToken: false, 50 | storageKey: name, 51 | storage: { 52 | getItem(key: string) { 53 | return JSON.stringify(currentSession); 54 | }, 55 | setItem(key: string, _value: string) { 56 | let session: Session = JSON.parse(_value); 57 | const value = stringifySupabaseSession(session); 58 | 59 | currentSession = session; 60 | 61 | setCookie(key, value, { 62 | domain, 63 | path, 64 | maxAge, 65 | // Allow supabase-js on the client to read the cookie as well 66 | httpOnly: false, 67 | sameSite, 68 | secure: secure ?? isSecureEnvironment(getRequestHeader('host')) 69 | }); 70 | }, 71 | removeItem(key: string) { 72 | // don't remove the session if there isn't one 73 | if (!currentSession) { 74 | return; 75 | } 76 | 77 | setCookie(key, '', { 78 | domain, 79 | path, 80 | expires: new Date(0), 81 | httpOnly: false, 82 | sameSite, 83 | secure: secure ?? isSecureEnvironment(getRequestHeader('host')) 84 | }); 85 | } 86 | } 87 | } 88 | }); 89 | } 90 | -------------------------------------------------------------------------------- /packages/sveltekit/src/supabaseServerClient.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CookieOptions, 3 | SupabaseClientOptionsWithoutAuth 4 | } from '@supabase/auth-helpers-shared'; 5 | import { createClient } from '@supabase/supabase-js'; 6 | import { RequestEvent } from '@sveltejs/kit'; 7 | import { supabaseAuthStorageAdapterSveltekitServer } from './serverStorageAdapter'; 8 | 9 | /** 10 | * ## Authenticated Supabase client 11 | * ### Handle 12 | * 13 | * ```ts 14 | * import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; 15 | * import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit'; 16 | * import type { Handle } from '@sveltejs/kit'; 17 | * 18 | * export const handle: Handle = async ({ event, resolve }) => { 19 | * event.locals.supabase = createSupabaseServerClient({ 20 | * supabaseUrl: PUBLIC_SUPABASE_URL, 21 | * supabaseKey: PUBLIC_SUPABASE_ANON_KEY, 22 | * event 23 | * }); 24 | * 25 | * event.locals.getSession = async () => { 26 | * const { 27 | * data: { session } 28 | * } = await event.locals.supabase.auth.getSession(); 29 | * return session; 30 | * }; 31 | * 32 | * return resolve(event, { 33 | * filterSerializedResponseHeaders(name) { 34 | * return name === 'content-range'; 35 | * } 36 | * }); 37 | * }; 38 | * ``` 39 | * 40 | * ### Types 41 | * 42 | * ```ts 43 | * import { SupabaseClient, Session } from '@supabase/supabase-js'; 44 | * 45 | * declare global { 46 | * namespace App { 47 | * interface Locals { 48 | * supabase: SupabaseClient; 49 | * getSession(): Promise; 50 | * } 51 | * // interface PageData {} 52 | * // interface Error {} 53 | * // interface Platform {} 54 | * } 55 | * } 56 | * 57 | * export {}; 58 | * ``` 59 | */ 60 | export function createSupabaseServerClient< 61 | Database = any, 62 | SchemaName extends string & keyof Database = 'public' extends keyof Database 63 | ? 'public' 64 | : string & keyof Database 65 | >({ 66 | supabaseUrl, 67 | supabaseKey, 68 | event, 69 | options, 70 | cookieOptions, 71 | expiryMargin 72 | }: { 73 | supabaseUrl: string; 74 | supabaseKey: string; 75 | event: Pick; 76 | options?: SupabaseClientOptionsWithoutAuth; 77 | cookieOptions?: CookieOptions; 78 | expiryMargin?: number; 79 | }) { 80 | return createClient(supabaseUrl, supabaseKey, { 81 | ...options, 82 | global: { 83 | ...options?.global, 84 | headers: { 85 | ...options?.global?.headers, 86 | 'X-Client-Info': `${PACKAGE_NAME}@${PACKAGE_VERSION}` 87 | } 88 | }, 89 | auth: { 90 | autoRefreshToken: false, 91 | detectSessionInUrl: false, 92 | persistSession: true, 93 | storage: supabaseAuthStorageAdapterSveltekitServer({ 94 | cookies: event.cookies, 95 | cookieOptions, 96 | expiryMargin 97 | }) 98 | } 99 | }); 100 | } 101 | -------------------------------------------------------------------------------- /packages/shared/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # shared 2 | 3 | ## 0.3.4 4 | 5 | ### Patch Changes 6 | 7 | - 04a7249: Add jose for it's cross platform base64url decode support 8 | - 04a7249: Remove js-base64 as its buffer check was causing issues for Vercel Edge Runtime 9 | 10 | ## 0.3.3 11 | 12 | ### Patch Changes 13 | 14 | - 1ea258e: use js-base64 15 | 16 | ## 0.3.2 17 | 18 | ### Patch Changes 19 | 20 | - 185e9cf: fix decodeBase64url 21 | 22 | ## 0.3.1 23 | 24 | ### Patch Changes 25 | 26 | - f86073d: Update to use custom Buffer implementation 27 | 28 | ## 0.3.0 29 | 30 | ### Minor Changes 31 | 32 | - 33c8a81: Adds MFA factors into session 33 | 34 | Factors and identities were removed from session on [PR #350](https://github.com/supabase/auth-helpers/pull/350). When retrieving the `aal` data using `getAuthenticatorAssuranceLevel` wrong data is returned 35 | 36 | ## 0.2.4 37 | 38 | ### Patch Changes 39 | 40 | - d3366e4: Allow passing client options 41 | 42 | ## 0.2.3 43 | 44 | ### Patch Changes 45 | 46 | - bee77c7: fix: session data comes back encoded #359 47 | 48 | ## 0.2.2 49 | 50 | ### Patch Changes 51 | 52 | - 999e57e: chore: reduce cookie size. 53 | 54 | WARNING: this patch changes the structure of the `supabase-auth-token` cookie. It is patched in a backwards compatible manner as long as your application doesn't access the cookie directly. If your application accesses the cookie directly, you will need to update your application to support the new cookies structure: 55 | 56 | ```js 57 | // The new shape of the `supabase-auth-token` cookie. 58 | JSON.stringify([ 59 | session.access_token, 60 | session.refresh_token, 61 | session.provider_token, 62 | session.provider_refresh_token 63 | ]); 64 | ``` 65 | 66 | ## 0.2.1 67 | 68 | ### Patch Changes 69 | 70 | - 2fda843: add missing supabase-js peerDependency 71 | - 2fda843: update supabase-js 72 | 73 | ## 0.2.0 74 | 75 | ### Minor Changes 76 | 77 | - fd30e33: Update to work with supabase-js v2 RC 78 | 79 | ### Patch Changes 80 | 81 | - 20fa944: add sveltekit supabase v2 support 82 | - fe5c4a6: chore: improve types. 83 | - 2fdb094: chore: types and middleware improvements. 84 | 85 | ## 0.2.0-next.3 86 | 87 | ### Patch Changes 88 | 89 | - 20fa944: add sveltekit supabase v2 support 90 | 91 | ## 0.2.0-next.2 92 | 93 | ### Patch Changes 94 | 95 | - 2fdb094: chore: types and middleware improvements. 96 | 97 | ## 0.2.0-next.1 98 | 99 | ### Patch Changes 100 | 101 | - fe5c4a6: chore: improve types. 102 | 103 | ## 0.2.0-next.0 104 | 105 | ### Minor Changes 106 | 107 | - 1b33e44: Update to work with supabase-js v2 RC 108 | 109 | ## 0.1.3 110 | 111 | ### Patch Changes 112 | 113 | - 56228e3: Add new error class for provider token not found 114 | 115 | ## 0.1.2 116 | 117 | ### Patch Changes 118 | 119 | - 38ccf1c: Add new to string method to the base error class 120 | 121 | ## 0.1.1 122 | 123 | ### Patch Changes 124 | 125 | - 9dda264: Add better error handling and error codes 126 | 127 | ## 0.1.0 128 | 129 | ### Minor Changes 130 | 131 | - a3c2991: Initial release of new library version 132 | -------------------------------------------------------------------------------- /packages/shared/src/utils/cookies.ts: -------------------------------------------------------------------------------- 1 | import { Session } from '@supabase/supabase-js'; 2 | import { parse, serialize } from 'cookie'; 3 | import { base64url } from 'jose'; 4 | 5 | export { parse as parseCookies, serialize as serializeCookie }; 6 | 7 | /** 8 | * Filter out the cookies based on a key 9 | */ 10 | export function filterCookies(cookies: string[], key: string) { 11 | const indexes = new Set( 12 | cookies 13 | .map((cookie) => parse(cookie)) 14 | .reduce((acc, cookie, i) => { 15 | if (key in cookie) { 16 | acc.push(i); 17 | } 18 | 19 | return acc; 20 | }, new Array()) 21 | ); 22 | 23 | return cookies.filter((_, i) => !indexes.has(i)); 24 | } 25 | 26 | /** 27 | * Based on the environment and the request we know if a secure cookie can be set. 28 | */ 29 | export function isSecureEnvironment(headerHost?: string | string[]) { 30 | if (!headerHost) { 31 | throw new Error('The "host" request header is not available'); 32 | } 33 | 34 | const headerHostStr = Array.isArray(headerHost) ? headerHost[0] : headerHost; 35 | 36 | const host = 37 | (headerHostStr.indexOf(':') > -1 && headerHostStr.split(':')[0]) || 38 | headerHostStr; 39 | if ( 40 | ['localhost', '127.0.0.1'].indexOf(host) > -1 || 41 | host.endsWith('.local') 42 | ) { 43 | return false; 44 | } 45 | 46 | return true; 47 | } 48 | 49 | export function parseSupabaseCookie( 50 | str: string | null | undefined 51 | ): Partial | null { 52 | if (!str) { 53 | return null; 54 | } 55 | 56 | try { 57 | const session = JSON.parse(str); 58 | if (!session) { 59 | return null; 60 | } 61 | // Support previous cookie which was a stringified session object. 62 | if (session.constructor.name === 'Object') { 63 | return session; 64 | } 65 | if (session.constructor.name !== 'Array') { 66 | throw new Error(`Unexpected format: ${session.constructor.name}`); 67 | } 68 | 69 | const [_header, payloadStr, _signature] = session[0].split('.'); 70 | const payload = base64url.decode(payloadStr); 71 | const decoder = new TextDecoder(); 72 | 73 | const { exp, sub, ...user } = JSON.parse(decoder.decode(payload)); 74 | 75 | return { 76 | expires_at: exp, 77 | expires_in: exp - Math.round(Date.now() / 1000), 78 | token_type: 'bearer', 79 | access_token: session[0], 80 | refresh_token: session[1], 81 | provider_token: session[2], 82 | provider_refresh_token: session[3], 83 | user: { 84 | id: sub, 85 | factors: session[4], 86 | ...user 87 | } 88 | }; 89 | } catch (err) { 90 | console.warn('Failed to parse cookie string:', err); 91 | return null; 92 | } 93 | } 94 | 95 | export function stringifySupabaseSession(session: Session): string { 96 | return JSON.stringify([ 97 | session.access_token, 98 | session.refresh_token, 99 | session.provider_token, 100 | session.provider_refresh_token, 101 | session.user.factors 102 | ]); 103 | } 104 | -------------------------------------------------------------------------------- /examples/remix/app/entry.server.tsx: -------------------------------------------------------------------------------- 1 | import { PassThrough } from 'stream'; 2 | import type { EntryContext } from '@remix-run/node'; 3 | import { Response } from '@remix-run/node'; 4 | import { RemixServer } from '@remix-run/react'; 5 | import isbot from 'isbot'; 6 | import { renderToPipeableStream } from 'react-dom/server'; 7 | 8 | const ABORT_DELAY = 5000; 9 | 10 | export default function handleRequest( 11 | request: Request, 12 | responseStatusCode: number, 13 | responseHeaders: Headers, 14 | remixContext: EntryContext 15 | ) { 16 | return isbot(request.headers.get('user-agent')) 17 | ? handleBotRequest( 18 | request, 19 | responseStatusCode, 20 | responseHeaders, 21 | remixContext 22 | ) 23 | : handleBrowserRequest( 24 | request, 25 | responseStatusCode, 26 | responseHeaders, 27 | remixContext 28 | ); 29 | } 30 | 31 | function handleBotRequest( 32 | request: Request, 33 | responseStatusCode: number, 34 | responseHeaders: Headers, 35 | remixContext: EntryContext 36 | ) { 37 | return new Promise((resolve, reject) => { 38 | let didError = false; 39 | 40 | const { pipe, abort } = renderToPipeableStream( 41 | , 42 | { 43 | onAllReady() { 44 | const body = new PassThrough(); 45 | 46 | responseHeaders.set('Content-Type', 'text/html'); 47 | 48 | resolve( 49 | new Response(body, { 50 | headers: responseHeaders, 51 | status: didError ? 500 : responseStatusCode 52 | }) 53 | ); 54 | 55 | pipe(body); 56 | }, 57 | onShellError(error: unknown) { 58 | reject(error); 59 | }, 60 | onError(error: unknown) { 61 | didError = true; 62 | 63 | console.error(error); 64 | } 65 | } 66 | ); 67 | 68 | setTimeout(abort, ABORT_DELAY); 69 | }); 70 | } 71 | 72 | function handleBrowserRequest( 73 | request: Request, 74 | responseStatusCode: number, 75 | responseHeaders: Headers, 76 | remixContext: EntryContext 77 | ) { 78 | return new Promise((resolve, reject) => { 79 | let didError = false; 80 | 81 | const { pipe, abort } = renderToPipeableStream( 82 | , 83 | { 84 | onShellReady() { 85 | const body = new PassThrough(); 86 | 87 | responseHeaders.set('Content-Type', 'text/html'); 88 | 89 | resolve( 90 | new Response(body, { 91 | headers: responseHeaders, 92 | status: didError ? 500 : responseStatusCode 93 | }) 94 | ); 95 | 96 | pipe(body); 97 | }, 98 | onShellError(err: unknown) { 99 | reject(err); 100 | }, 101 | onError(error: unknown) { 102 | didError = true; 103 | 104 | console.error(error); 105 | } 106 | } 107 | ); 108 | 109 | setTimeout(abort, ABORT_DELAY); 110 | }); 111 | } 112 | -------------------------------------------------------------------------------- /examples/nextjs-server-components/app/page.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 2rem; 3 | } 4 | 5 | .main { 6 | min-height: 100vh; 7 | padding: 4rem 0; 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .footer { 16 | display: flex; 17 | flex: 1; 18 | padding: 2rem 0; 19 | border-top: 1px solid #eaeaea; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .footer a { 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | flex-grow: 1; 29 | } 30 | 31 | .title { 32 | margin: 0; 33 | line-height: 1.15; 34 | font-size: 4rem; 35 | font-style: normal; 36 | font-weight: 800; 37 | letter-spacing: -0.025em; 38 | } 39 | 40 | .title a { 41 | text-decoration: none; 42 | color: #0070f3; 43 | } 44 | 45 | .title a:hover, 46 | .title a:focus, 47 | .title a:active { 48 | text-decoration: underline; 49 | } 50 | 51 | .title, 52 | .description { 53 | text-align: center; 54 | } 55 | 56 | .description { 57 | margin: 4rem 0; 58 | line-height: 1.5; 59 | font-size: 1.5rem; 60 | } 61 | 62 | .code { 63 | background: #fafafa; 64 | border-radius: 5px; 65 | padding: 0.75rem; 66 | font-size: 1.1rem; 67 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 68 | Bitstream Vera Sans Mono, Courier New, monospace; 69 | } 70 | 71 | .grid { 72 | display: flex; 73 | align-items: center; 74 | justify-content: center; 75 | flex-wrap: wrap; 76 | max-width: 1200px; 77 | } 78 | 79 | .card { 80 | margin: 1rem; 81 | padding: 1.5rem; 82 | text-align: left; 83 | color: inherit; 84 | text-decoration: none; 85 | border: 1px solid #eaeaea; 86 | border-radius: 10px; 87 | transition: color 0.15s ease, border-color 0.15s ease; 88 | max-width: 300px; 89 | } 90 | 91 | .card:hover, 92 | .card:focus, 93 | .card:active { 94 | color: #0070f3; 95 | border-color: #0070f3; 96 | } 97 | 98 | .card h2 { 99 | margin: 0 0 1rem 0; 100 | font-size: 1.5rem; 101 | } 102 | 103 | .card p { 104 | margin: 0; 105 | font-size: 1.25rem; 106 | line-height: 1.5; 107 | } 108 | 109 | .logo { 110 | height: 1em; 111 | margin-left: 0.5rem; 112 | } 113 | 114 | @media (max-width: 600px) { 115 | .grid { 116 | width: 100%; 117 | flex-direction: column; 118 | } 119 | } 120 | 121 | @media (prefers-color-scheme: dark) { 122 | .title { 123 | background: linear-gradient(180deg, #ffffff 0%, #aaaaaa 100%); 124 | -webkit-background-clip: text; 125 | -webkit-text-fill-color: transparent; 126 | background-clip: text; 127 | text-fill-color: transparent; 128 | } 129 | .title a { 130 | background: linear-gradient(180deg, #0070f3 0%, #0153af 100%); 131 | -webkit-background-clip: text; 132 | -webkit-text-fill-color: transparent; 133 | background-clip: text; 134 | text-fill-color: transparent; 135 | } 136 | .card, 137 | .footer { 138 | border-color: #222; 139 | } 140 | .code { 141 | background: #111; 142 | } 143 | .logo img { 144 | filter: invert(1); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /examples/remix/app/routes/__supabase.tsx: -------------------------------------------------------------------------------- 1 | import { json } from '@remix-run/node'; 2 | import { Outlet, useFetcher, useLoaderData } from '@remix-run/react'; 3 | import { createBrowserClient } from '@supabase/auth-helpers-remix'; 4 | import Login from 'components/login'; 5 | import Nav from 'components/nav'; 6 | import { useEffect, useState } from 'react'; 7 | import { createServerClient } from 'utils/supabase.server'; 8 | 9 | import type { SupabaseClient, Session } from '@supabase/auth-helpers-remix'; 10 | import type { Database } from 'db_types'; 11 | import type { LoaderArgs } from '@remix-run/node'; 12 | 13 | export type TypedSupabaseClient = SupabaseClient; 14 | export type MaybeSession = Session | null; 15 | 16 | export type SupabaseContext = { 17 | supabase: TypedSupabaseClient; 18 | session: MaybeSession; 19 | }; 20 | 21 | // this uses Pathless Layout Routes [1] to wrap up all our Supabase logic 22 | 23 | // [1] https://remix.run/docs/en/v1/guides/routing#pathless-layout-routes 24 | 25 | export const loader = async ({ request }: LoaderArgs) => { 26 | // environment variables may be stored somewhere other than 27 | // `process.env` in runtimes other than node 28 | // we need to pipe these Supabase environment variables to the browser 29 | const env = { 30 | SUPABASE_URL: process.env.SUPABASE_URL!, 31 | SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY! 32 | }; 33 | 34 | // We can retrieve the session on the server and hand it to the client. 35 | // This is used to make sure the session is available immediately upon rendering 36 | const response = new Response(); 37 | 38 | const supabase = createServerClient({ request, response }); 39 | 40 | const { 41 | data: { session } 42 | } = await supabase.auth.getSession(); 43 | 44 | // in order for the set-cookie header to be set, 45 | // headers must be returned as part of the loader response 46 | return json( 47 | { 48 | env, 49 | session 50 | }, 51 | { 52 | headers: response.headers 53 | } 54 | ); 55 | }; 56 | 57 | export default function Supabase() { 58 | const { env, session } = useLoaderData(); 59 | const fetcher = useFetcher(); 60 | 61 | // it is important to create a single instance of Supabase 62 | // to use across client components - outlet context 👇 63 | const [supabase] = useState(() => 64 | createBrowserClient(env.SUPABASE_URL, env.SUPABASE_ANON_KEY) 65 | ); 66 | 67 | const serverAccessToken = session?.access_token; 68 | 69 | useEffect(() => { 70 | const { 71 | data: { subscription } 72 | } = supabase.auth.onAuthStateChange((event, session) => { 73 | if (session?.access_token !== serverAccessToken) { 74 | // server and client are out of sync. 75 | // Remix recalls active loaders after actions complete 76 | fetcher.submit(null, { 77 | method: 'post', 78 | action: '/handle-supabase-auth' 79 | }); 80 | } 81 | }); 82 | 83 | return () => { 84 | subscription.unsubscribe(); 85 | }; 86 | }, [serverAccessToken, supabase, fetcher]); 87 | 88 | return ( 89 | <> 90 | 91 |