(
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 |
6 |
7 | Optional Session ✅
8 |
9 |
10 | Required Session 👮♀️
11 |
12 |
13 | Realtime ⏱️
14 |
15 |
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 | Sign out
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 |
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 | {
28 | await supabaseClient.auth.signOut();
29 | router.push('/');
30 | }}
31 | >
32 | Logout
33 |
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 |
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 | Logout
41 | ) : (
42 | <>
43 | Email Login
44 | GitHub Login
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 |
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 | Logout
43 | ) : (
44 | <>
45 | Email Login
46 | GitHub Login
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 |
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 |
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 | {
28 | supabase.auth.signInWithOAuth({
29 | provider: 'github',
30 | options: { scopes: 'public_repo user:email' }
31 | });
32 | }}
33 | >
34 | GitHub with scopes
35 |
36 | {
38 | supabase.auth.signInWithOAuth({
39 | provider: 'google',
40 | options: { scopes: 'https://www.googleapis.com/auth/userinfo.email' }
41 | });
42 | }}
43 | >
44 | Google
45 |
46 | {:else}
47 |
48 | [withPageAuth ] | [supabaseServerClient ] | [GitHub Token ] |
51 | supabase.auth.updateUser({ data: { test5: 'updated' } })}>
52 | Update
53 |
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 |
7 |
8 |
12 |
13 |
14 | Get started by editing{' '}
15 | app/page.tsx
16 |
17 |
18 |
34 |
35 |
36 |
48 |
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 |
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 |
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 | {
35 | supabaseClient.auth.signInWithOAuth({
36 | provider: 'github',
37 | options: { scopes: 'repo', redirectTo: 'http://localhost:3000' }
38 | });
39 | }}
40 | >
41 | Login with github
42 |
43 |
52 | >
53 | );
54 |
55 | return (
56 | <>
57 |
58 | [ getServerSideProps] | [
59 | server-side RLS] |{' '}
60 |
62 | supabaseClient.auth.updateUser({ data: { test1: 'updated' } })
63 | }
64 | >
65 | Update user metadata
66 |
67 | supabaseClient.auth.refreshSession()}>
68 | Refresh session
69 |
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 |
92 |
93 | >
94 | );
95 | }
96 |
--------------------------------------------------------------------------------
/packages/sveltekit/src/supabaseLoadClient.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CookieOptions,
3 | isBrowser,
4 | SupabaseClientOptionsWithoutAuth
5 | } from '@supabase/auth-helpers-shared';
6 | import {
7 | AuthChangeEvent,
8 | createClient,
9 | Session,
10 | Subscription,
11 | SupabaseClient
12 | } from '@supabase/supabase-js';
13 | import { LoadEvent } from '@sveltejs/kit';
14 | import { supabaseAuthStorageAdapterSveltekitLoad } from './loadStorageAdapter';
15 |
16 | let cachedBrowserClient: SupabaseClient | undefined;
17 | let onAuthStateChangeSubscription: Subscription | undefined;
18 |
19 | /**
20 | * ## Authenticated Supabase client
21 | *
22 | * Returns a new authenticated Supabase client.
23 | *
24 | * When running in the browser it will create a single instance
25 | * that is returned for subsequent runs.
26 | *
27 | * ### Example:
28 | *
29 | * ```ts
30 | * import { invalidate } from '$app/navigation';
31 | * import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public';
32 | * import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit';
33 | * import type { LayoutLoad } from './$types';
34 | *
35 | * export const load: LayoutLoad = async ({ fetch, data, depends }) => {
36 | * depends('supabase:auth');
37 | *
38 | * const supabase = createSupabaseLoadClient({
39 | * supabaseUrl: PUBLIC_SUPABASE_URL,
40 | * supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
41 | * event: { fetch },
42 | * serverSession: data.session,
43 | * onAuthStateChange() {
44 | * invalidate('supabase:auth');
45 | * }
46 | * });
47 | *
48 | * const {
49 | * data: { session }
50 | * } = await supabase.auth.getSession();
51 | *
52 | * return { supabase, session };
53 | * };
54 | *
55 | * ```
56 | */
57 | export function createSupabaseLoadClient<
58 | Database = any,
59 | SchemaName extends string & keyof Database = 'public' extends keyof Database
60 | ? 'public'
61 | : string & keyof Database
62 | >({
63 | supabaseUrl,
64 | supabaseKey,
65 | event,
66 | serverSession,
67 | options,
68 | cookieOptions,
69 | onAuthStateChange
70 | }: {
71 | supabaseUrl: string;
72 | /**
73 | * The supabase key. Make sure you **always** use the ANON_KEY.
74 | */
75 | supabaseKey: string;
76 | event: Pick;
77 | /**
78 | * The initial session from the server.
79 | */
80 | serverSession: Session | null;
81 | options?: SupabaseClientOptionsWithoutAuth;
82 | cookieOptions?: CookieOptions;
83 | /**
84 | * The event listener only runs in the browser.
85 | * @deprecated DO NOT USE THIS
86 | *
87 | * use this instead: https://supabase.com/docs/guides/auth/auth-helpers/sveltekit#setting-up-the-event-listener-on-the-client-side
88 | */
89 | onAuthStateChange?: (event: AuthChangeEvent, session: Session | null) => void;
90 | }): SupabaseClient {
91 | const browser = isBrowser();
92 | if (browser && cachedBrowserClient) {
93 | return cachedBrowserClient as SupabaseClient;
94 | }
95 |
96 | // this should never happen
97 | onAuthStateChangeSubscription?.unsubscribe();
98 |
99 | const client = createClient(supabaseUrl, supabaseKey, {
100 | ...options,
101 | global: {
102 | fetch: event.fetch,
103 | ...options?.global,
104 | headers: {
105 | ...options?.global?.headers,
106 | 'X-Client-Info': `${PACKAGE_NAME}@${PACKAGE_VERSION}`
107 | }
108 | },
109 | auth: {
110 | autoRefreshToken: browser,
111 | detectSessionInUrl: browser,
112 | persistSession: true,
113 | storage: supabaseAuthStorageAdapterSveltekitLoad({
114 | cookieOptions,
115 | serverSession
116 | })
117 | }
118 | });
119 |
120 | if (browser) {
121 | cachedBrowserClient = client;
122 | onAuthStateChangeSubscription = onAuthStateChange
123 | ? cachedBrowserClient.auth.onAuthStateChange((event, authSession) => {
124 | onAuthStateChange?.(event, authSession);
125 | }).data.subscription
126 | : undefined;
127 | }
128 |
129 | return client;
130 | }
131 |
--------------------------------------------------------------------------------
/packages/react/src/components/SessionContext.tsx:
--------------------------------------------------------------------------------
1 | import { AuthError, Session, SupabaseClient } from '@supabase/supabase-js';
2 | import React, {
3 | createContext,
4 | PropsWithChildren,
5 | useContext,
6 | useEffect,
7 | useMemo,
8 | useState
9 | } from 'react';
10 |
11 | export type SessionContext =
12 | | {
13 | isLoading: true;
14 | session: null;
15 | error: null;
16 | supabaseClient: SupabaseClient;
17 | }
18 | | {
19 | isLoading: false;
20 | session: Session;
21 | error: null;
22 | supabaseClient: SupabaseClient;
23 | }
24 | | {
25 | isLoading: false;
26 | session: null;
27 | error: AuthError;
28 | supabaseClient: SupabaseClient;
29 | }
30 | | {
31 | isLoading: false;
32 | session: null;
33 | error: null;
34 | supabaseClient: SupabaseClient;
35 | };
36 |
37 | const SessionContext = createContext({
38 | isLoading: true,
39 | session: null,
40 | error: null,
41 | supabaseClient: {} as any
42 | });
43 |
44 | export interface SessionContextProviderProps {
45 | supabaseClient: SupabaseClient;
46 | initialSession?: Session | null;
47 | }
48 |
49 | export const SessionContextProvider = ({
50 | supabaseClient,
51 | initialSession = null,
52 | children
53 | }: PropsWithChildren) => {
54 | const [session, setSession] = useState(initialSession);
55 | const [isLoading, setIsLoading] = useState(!initialSession);
56 | const [error, setError] = useState();
57 |
58 | useEffect(() => {
59 | let mounted = true;
60 |
61 | async function getSession() {
62 | const {
63 | data: { session },
64 | error
65 | } = await supabaseClient.auth.getSession();
66 |
67 | // only update the react state if the component is still mounted
68 | if (mounted) {
69 | if (error) {
70 | setError(error);
71 | setIsLoading(false);
72 | return;
73 | }
74 |
75 | setSession(session);
76 | setIsLoading(false);
77 | }
78 | }
79 |
80 | getSession();
81 |
82 | return () => {
83 | mounted = false;
84 | }
85 | }, []);
86 |
87 | useEffect(() => {
88 | const {
89 | data: { subscription }
90 | } = supabaseClient.auth.onAuthStateChange((event, session) => {
91 | if (session && (event === 'SIGNED_IN' || event === 'TOKEN_REFRESHED')) {
92 | setSession(session);
93 | }
94 |
95 | if (event === 'SIGNED_OUT') {
96 | setSession(null);
97 | }
98 | });
99 |
100 | return () => {
101 | subscription.unsubscribe();
102 | };
103 | }, []);
104 |
105 | const value: SessionContext = useMemo(() => {
106 | if (isLoading) {
107 | return {
108 | isLoading: true,
109 | session: null,
110 | error: null,
111 | supabaseClient
112 | };
113 | }
114 |
115 | if (error) {
116 | return {
117 | isLoading: false,
118 | session: null,
119 | error,
120 | supabaseClient
121 | };
122 | }
123 |
124 | return {
125 | isLoading: false,
126 | session,
127 | error: null,
128 | supabaseClient
129 | };
130 | }, [isLoading, session, error]);
131 |
132 | return (
133 | {children}
134 | );
135 | };
136 |
137 | export const useSessionContext = () => {
138 | const context = useContext(SessionContext);
139 | if (context === undefined) {
140 | throw new Error(
141 | `useSessionContext must be used within a SessionContextProvider.`
142 | );
143 | }
144 |
145 | return context;
146 | };
147 |
148 | export function useSupabaseClient<
149 | Database = any,
150 | SchemaName extends string & keyof Database = 'public' extends keyof Database
151 | ? 'public'
152 | : string & keyof Database
153 | >() {
154 | const context = useContext(SessionContext);
155 | if (context === undefined) {
156 | throw new Error(
157 | `useSupabaseClient must be used within a SessionContextProvider.`
158 | );
159 | }
160 |
161 | return context.supabaseClient as SupabaseClient;
162 | }
163 |
164 | export const useSession = () => {
165 | const context = useContext(SessionContext);
166 | if (context === undefined) {
167 | throw new Error(`useSession must be used within a SessionContextProvider.`);
168 | }
169 |
170 | return context.session;
171 | };
172 |
173 | export const useUser = () => {
174 | const context = useContext(SessionContext);
175 | if (context === undefined) {
176 | throw new Error(`useUser must be used within a SessionContextProvider.`);
177 | }
178 |
179 | return context.session?.user ?? null;
180 | };
181 |
--------------------------------------------------------------------------------
/packages/nextjs/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @supabase/auth-helpers-nextjs
2 |
3 | ## 0.6.1
4 |
5 | ### Patch Changes
6 |
7 | - Updated dependencies [04a7249]
8 | - Updated dependencies [04a7249]
9 | - @supabase/auth-helpers-shared@0.3.4
10 |
11 | ## 0.6.0
12 |
13 | ### Minor Changes
14 |
15 | - 50d0a16: Remove deprecated methods from the nextjs package
16 |
17 | ## 0.5.9
18 |
19 | ### Patch Changes
20 |
21 | - Updated dependencies [1ea258e]
22 | - @supabase/auth-helpers-shared@0.3.3
23 |
24 | ## 0.5.8
25 |
26 | ### Patch Changes
27 |
28 | - Updated dependencies [185e9cf]
29 | - @supabase/auth-helpers-shared@0.3.2
30 |
31 | ## 0.5.7
32 |
33 | ### Patch Changes
34 |
35 | - Updated dependencies [f86073d]
36 | - @supabase/auth-helpers-shared@0.3.1
37 |
38 | ## 0.5.6
39 |
40 | ### Patch Changes
41 |
42 | - 5ab18fe: Export function to create Supabase Client in Next.js Route Handler functions
43 |
44 | ## 0.5.5
45 |
46 | ### Patch Changes
47 |
48 | - Updated dependencies [33c8a81]
49 | - @supabase/auth-helpers-shared@0.3.0
50 |
51 | ## 0.5.4
52 |
53 | ### Patch Changes
54 |
55 | - 849e447: allow overwriting client url and key
56 |
57 | ## 0.5.3
58 |
59 | ### Patch Changes
60 |
61 | - d3366e4: Allow passing client options
62 | - Updated dependencies [d3366e4]
63 | - @supabase/auth-helpers-shared@0.2.4
64 |
65 | ## 0.5.2
66 |
67 | ### Patch Changes
68 |
69 | - 2be3f10: feat: add helper for Next.js Server Components.
70 |
71 | ## 0.5.1
72 |
73 | ### Patch Changes
74 |
75 | - Updated dependencies [bee77c7]
76 | - @supabase/auth-helpers-shared@0.2.3
77 |
78 | ## 0.5.0
79 |
80 | ### Minor Changes
81 |
82 | - d8f7446: chore: deprecate withApiAuth, withPageAuth, and withMiddlewareAuth.
83 |
84 | ## 0.4.5
85 |
86 | ### Patch Changes
87 |
88 | - d6c43ef: fix: "host" request header is not available. #358
89 |
90 | ## 0.4.4
91 |
92 | ### Patch Changes
93 |
94 | - 5cdc84c: Fix a bug with withPageAuth when a session is null and authRequired is set to false
95 |
96 | ## 0.4.3
97 |
98 | ### Patch Changes
99 |
100 | - Updated dependencies [999e57e]
101 | - @supabase/auth-helpers-shared@0.2.2
102 |
103 | ## 0.4.2
104 |
105 | ### Patch Changes
106 |
107 | - 2fda843: add missing supabase-js peerDependency
108 | - 2fda843: update supabase-js
109 | - Updated dependencies [2fda843]
110 | - Updated dependencies [2fda843]
111 | - @supabase/auth-helpers-shared@0.2.1
112 |
113 | ## 0.4.1
114 |
115 | ### Patch Changes
116 |
117 | - 5140f5a: fix: withPageAuth return user. [#314](https://github.com/supabase/auth-helpers/issues/314)
118 |
119 | ## 0.4.0
120 |
121 | ### Minor Changes
122 |
123 | - fd30e33: Update to work with supabase-js v2 RC
124 |
125 | ### Patch Changes
126 |
127 | - 20fa944: add sveltekit supabase v2 support
128 | - fe5c4a6: chore: improve types.
129 | - 2fdb094: chore: types and middleware improvements.
130 | - af28db1: chore: export middleware at root.
131 | - Updated dependencies [20fa944]
132 | - Updated dependencies [fd30e33]
133 | - Updated dependencies [fe5c4a6]
134 | - Updated dependencies [2fdb094]
135 | - @supabase/auth-helpers-shared@0.2.0
136 |
137 | ## 0.4.0-next.4
138 |
139 | ### Patch Changes
140 |
141 | - 20fa944: add sveltekit supabase v2 support
142 | - Updated dependencies [20fa944]
143 | - @supabase/auth-helpers-shared@0.2.0-next.3
144 |
145 | ## 0.4.0-next.3
146 |
147 | ### Patch Changes
148 |
149 | - 2fdb094: chore: types and middleware improvements.
150 | - Updated dependencies [2fdb094]
151 | - @supabase/auth-helpers-shared@0.2.0-next.2
152 |
153 | ## 0.4.0-next.2
154 |
155 | ### Patch Changes
156 |
157 | - fe5c4a6: chore: improve types.
158 | - Updated dependencies [fe5c4a6]
159 | - @supabase/auth-helpers-shared@0.2.0-next.1
160 |
161 | ## 0.4.0-next.1
162 |
163 | ### Patch Changes
164 |
165 | - af28db1: chore: export middleware at root.
166 |
167 | ## 0.4.0-next.0
168 |
169 | ### Minor Changes
170 |
171 | - 1b33e44: Update to work with supabase-js v2 RC
172 |
173 | ### Patch Changes
174 |
175 | - Updated dependencies [1b33e44]
176 | - @supabase/auth-helpers-shared@0.2.0-next.0
177 |
178 | ## 0.2.7
179 |
180 | ### Patch Changes
181 |
182 | - 0ab05cf: Update format of x-client-info header
183 |
184 | ## 0.2.6
185 |
186 | ### Patch Changes
187 |
188 | - 50669a6: Add x-client-info header to show package name and version
189 |
190 | ## 0.2.5
191 |
192 | ### Patch Changes
193 |
194 | - 8e0b747: Change error handling to not show flow disruption errors
195 |
196 | ## 0.2.4
197 |
198 | ### Patch Changes
199 |
200 | - 56228e3: Add getProviderToken helper method
201 | - Updated dependencies [56228e3]
202 | - @supabase/auth-helpers-shared@0.1.3
203 |
204 | ## 0.2.3
205 |
206 | ### Patch Changes
207 |
208 | - 69fefcb: Change logger to be a wrapper for console
209 |
210 | ## 0.2.2
211 |
212 | ### Patch Changes
213 |
214 | - 38ccf1c: Logger can be imported in your own project for setting log levels
215 | - Updated dependencies [38ccf1c]
216 | - @supabase/auth-helpers-shared@0.1.2
217 |
218 | ## 0.2.1
219 |
220 | ### Patch Changes
221 |
222 | - 9dda264: Add better error handling and error codes
223 | - Updated dependencies [9dda264]
224 | - @supabase/auth-helpers-shared@0.1.1
225 |
226 | ## 0.2.0
227 |
228 | ### Minor Changes
229 |
230 | - f399820: Using shared package as a dependency
231 | Update sveltekit package with latest code to update tokens
232 |
233 | ## 0.1.0
234 |
235 | ### Minor Changes
236 |
237 | - a3c2991: Initial release of new library version
238 |
--------------------------------------------------------------------------------
/packages/remix/src/utils/createSupabaseClient.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CookieOptions,
3 | createServerSupabaseClient,
4 | parseCookies,
5 | serializeCookie,
6 | createBrowserSupabaseClient,
7 | SupabaseClientOptionsWithoutAuth
8 | } from '@supabase/auth-helpers-shared';
9 | import { SupabaseClient } from '@supabase/supabase-js';
10 |
11 | /**
12 | * ## Authenticated Supabase client
13 | * ### Loader
14 | *
15 | * ```ts
16 | * import { createServerClient } from '@supabase/auth-helpers-remix';
17 | *
18 | * export const loader = async ({ request }: { request: Request }) => {
19 | * const response = new Response();
20 | *
21 | * const supabaseClient = createServerClient(
22 | * process.env.SUPABASE_URL,
23 | * process.env.SUPABASE_ANON_KEY,
24 | * { request, response }
25 | * );
26 | *
27 | * const { data } = await supabaseClient.from('test').select('*');
28 | *
29 | * return json(
30 | * { data },
31 | * { headers: response.headers }
32 | * );
33 | * };
34 | * ```
35 | *
36 | * ### Action
37 | *
38 | * ```ts
39 | * import { createServerClient } from '@supabase/auth-helpers-remix';
40 | *
41 | * export const action = async ({ request }: { request: Request }) => {
42 | * const response = new Response();
43 | *
44 | * const supabaseClient = createServerClient(
45 | * process.env.SUPABASE_URL,
46 | * process.env.SUPABASE_ANON_KEY,
47 | * { request, response }
48 | * );
49 | *
50 | * const { data } = await supabaseClient.from('test').select('*');
51 | *
52 | * return json(
53 | * { data },
54 | * { headers: response.headers }
55 | * );
56 | * };
57 | * ```
58 | *
59 | * ### Component
60 | *
61 | * ```ts
62 | * import { createBrowserClient } from '@supabase/auth-helpers-remix';
63 | *
64 | * useEffect(() => {
65 | * const supabaseClient = createBrowserClient(
66 | * window.env.SUPABASE_URL,
67 | * window.env.SUPABASE_ANON_KEY
68 | * );
69 | *
70 | * const getData = async () => {
71 | * const { data: supabaseData } = await supabaseClient
72 | * .from('test')
73 | * .select('*');
74 | *
75 | * console.log({ data });
76 | * };
77 | *
78 | * getData();
79 | * }, []);
80 | * ```
81 | *
82 | * Note: window.env is not automatically populated by Remix
83 | * Check out the [example app](../../../../examples/remix/app/root.tsx) or
84 | * [Remix docs](https://remix.run/docs/en/v1/guides/envvars#browser-environment-variables) for more info
85 | */
86 |
87 | export function createBrowserClient<
88 | Database = any,
89 | SchemaName extends string & keyof Database = 'public' extends keyof Database
90 | ? 'public'
91 | : string & keyof Database
92 | >(
93 | supabaseUrl: string,
94 | supabaseKey: string,
95 | {
96 | options,
97 | cookieOptions
98 | }: {
99 | options?: SupabaseClientOptionsWithoutAuth;
100 | cookieOptions?: CookieOptions;
101 | } = {}
102 | ): SupabaseClient {
103 | if (!supabaseUrl || !supabaseKey) {
104 | throw new Error(
105 | 'supabaseUrl and supabaseKey are required to create a Supabase client! Find these under `Settings` > `API` in your Supabase dashboard.'
106 | );
107 | }
108 |
109 | return createBrowserSupabaseClient({
110 | supabaseUrl,
111 | supabaseKey,
112 | options: {
113 | ...options,
114 | global: {
115 | ...options?.global,
116 | headers: {
117 | ...options?.global?.headers,
118 | 'X-Client-Info': `${PACKAGE_NAME}@${PACKAGE_VERSION}`
119 | }
120 | }
121 | },
122 | cookieOptions
123 | });
124 | }
125 |
126 | export function createServerClient<
127 | Database = any,
128 | SchemaName extends string & keyof Database = 'public' extends keyof Database
129 | ? 'public'
130 | : string & keyof Database
131 | >(
132 | supabaseUrl: string,
133 | supabaseKey: string,
134 | {
135 | request,
136 | response,
137 | options,
138 | cookieOptions
139 | }: {
140 | request: Request;
141 | response: Response;
142 | options?: SupabaseClientOptionsWithoutAuth;
143 | cookieOptions?: CookieOptions;
144 | }
145 | ): SupabaseClient {
146 | if (!supabaseUrl || !supabaseKey) {
147 | throw new Error(
148 | 'supabaseUrl and supabaseKey are required to create a Supabase client! Find these under `Settings` > `API` in your Supabase dashboard.'
149 | );
150 | }
151 |
152 | if (!request || !response) {
153 | throw new Error(
154 | 'request and response must be passed to createSupabaseClient function, when called from loader or action'
155 | );
156 | }
157 |
158 | return createServerSupabaseClient({
159 | supabaseUrl,
160 | supabaseKey,
161 | getRequestHeader: (key) => {
162 | return request.headers.get(key) ?? undefined;
163 | },
164 | getCookie: (name) => {
165 | return parseCookies(request?.headers?.get('Cookie') ?? '')[name];
166 | },
167 | setCookie(name, value, options) {
168 | const cookieStr = serializeCookie(name, value, {
169 | ...options,
170 | // Allow supabase-js on the client to read the cookie as well
171 | httpOnly: false
172 | });
173 | response.headers.set('set-cookie', cookieStr);
174 | },
175 | options: {
176 | ...options,
177 | global: {
178 | ...options?.global,
179 | headers: {
180 | ...options?.global?.headers,
181 | 'X-Client-Info': `${PACKAGE_NAME}@${PACKAGE_VERSION}`
182 | }
183 | }
184 | },
185 | cookieOptions
186 | });
187 | }
188 |
--------------------------------------------------------------------------------
/packages/sveltekit/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @supabase/auth-helpers-sveltekit
2 |
3 | ## 0.9.4
4 |
5 | ### Patch Changes
6 |
7 | - Updated dependencies [04a7249]
8 | - Updated dependencies [04a7249]
9 | - @supabase/auth-helpers-shared@0.3.4
10 |
11 | ## 0.9.3
12 |
13 | ### Patch Changes
14 |
15 | - Updated dependencies [1ea258e]
16 | - @supabase/auth-helpers-shared@0.3.3
17 |
18 | ## 0.9.2
19 |
20 | ### Patch Changes
21 |
22 | - Updated dependencies [185e9cf]
23 | - @supabase/auth-helpers-shared@0.3.2
24 |
25 | ## 0.9.1
26 |
27 | ### Patch Changes
28 |
29 | - Updated dependencies [f86073d]
30 | - @supabase/auth-helpers-shared@0.3.1
31 |
32 | ## 0.9.0
33 |
34 | ### Minor Changes
35 |
36 | - 192f85c: SvelteKit rework:
37 | - separate server and client authentication [9bf88f7](https://github.com/supabase/auth-helpers/pull/457/commits/9bf88f76d3124f758394720de84c722052660546)
38 | - update supabase-js to 2.7.0 [6a18a94](https://github.com/supabase/auth-helpers/pull/457/commits/6a18a940f4e5ffde022580daf37ba40750637ec1)
39 | - update examples [0bef909](https://github.com/supabase/auth-helpers/pull/457/commits/0bef909d912efdf020b24a9d653d2f34080cd107)
40 | - use tsup for constants [1da1f67](https://github.com/supabase/auth-helpers/pull/457/commits/1da1f672c020b55103e564137150932b1c122b2c)
41 |
42 | ### Patch Changes
43 |
44 | - Updated dependencies [33c8a81]
45 | - @supabase/auth-helpers-shared@0.3.0
46 |
47 | ## 0.8.7
48 |
49 | ### Patch Changes
50 |
51 | - d3366e4: Allow passing client options
52 | - Updated dependencies [d3366e4]
53 | - @supabase/auth-helpers-shared@0.2.4
54 |
55 | ## 0.8.6
56 |
57 | ### Patch Changes
58 |
59 | - Updated dependencies [bee77c7]
60 | - @supabase/auth-helpers-shared@0.2.3
61 |
62 | ## 0.8.5
63 |
64 | ### Patch Changes
65 |
66 | - Updated dependencies [999e57e]
67 | - @supabase/auth-helpers-shared@0.2.2
68 |
69 | ## 0.8.4
70 |
71 | ### Patch Changes
72 |
73 | - 075a92f: sveltekit ssr disable auto token refresh
74 |
75 | ## 0.8.3
76 |
77 | ### Patch Changes
78 |
79 | - 2fda843: Switch to refreshSession
80 | - 2fda843: update supabase-js
81 | - Updated dependencies [2fda843]
82 | - Updated dependencies [2fda843]
83 | - @supabase/auth-helpers-shared@0.2.1
84 |
85 | ## 0.8.2
86 |
87 | ### Patch Changes
88 |
89 | - 8f6a474: Fix type issue in the getServerSession function
90 |
91 | ## 0.8.1
92 |
93 | ### Patch Changes
94 |
95 | - 7a6377a: Add fix for esm package build
96 | - 7a6377a: Remove old files from 0.7.x
97 |
98 | ## 0.8.0
99 |
100 | ### Minor Changes
101 |
102 | - 58e6287: Update to supabase-js v2
103 |
104 | ## 0.7.1
105 |
106 | ### Patch Changes
107 |
108 | - 3a09401: Add type intellisense for @supabase/auth-helpers-sveltekit/server
109 | Add .js to fix ERR_MODULE_NOT_FOUND (can´t use the script, it throws an error @sveltejs/kit/hooks not found)
110 | Pass page store and invalidation to startSupabaseSessionSync as \$app module is not available in npm packages
111 |
112 | ## 0.7.0
113 |
114 | ### Minor Changes
115 |
116 | - cd9150c: [breaking] Update to work with latest SvelteKit RC
117 |
118 | ## 0.6.11
119 |
120 | ### Patch Changes
121 |
122 | - Updated dependencies [63b1da0]
123 | - @supabase/auth-helpers-shared@0.1.4
124 |
125 | ## 0.6.10
126 |
127 | ### Patch Changes
128 |
129 | - 0ab05cf: Update format of x-client-info header
130 |
131 | ## 0.6.9
132 |
133 | ### Patch Changes
134 |
135 | - 50669a6: Add x-client-info header to show package name and version
136 |
137 | ## 0.6.8
138 |
139 | ### Patch Changes
140 |
141 | - 8e0b747: Change error handling to not show flow disruption errors
142 |
143 | ## 0.6.7
144 |
145 | ### Patch Changes
146 |
147 | - 0298db1: Rename skHelper method to createSupabaseClient
148 |
149 | ## 0.6.6
150 |
151 | ### Patch Changes
152 |
153 | - 56228e3: Remove the need for user to create /api/auth/user and /api/auth/callback endpoints
154 | - 56228e3: Add getProviderToken helper method
155 | - Updated dependencies [56228e3]
156 | - @supabase/auth-helpers-shared@0.1.3
157 |
158 | ## 0.6.5
159 |
160 | ### Patch Changes
161 |
162 | - 69fefcb: Change logger to be a wrapper for console
163 |
164 | ## 0.6.4
165 |
166 | ### Patch Changes
167 |
168 | - 38ccf1c: Logger can be imported in your own project for setting log levels
169 | - Updated dependencies [38ccf1c]
170 | - @supabase/auth-helpers-shared@0.1.2
171 |
172 | ## 0.6.3
173 |
174 | ### Patch Changes
175 |
176 | - 9dda264: Add better error handling and error codes
177 | - 9dda264: Fix supabaseServerClient with request and withApiAuth return type
178 | - Updated dependencies [9dda264]
179 | - @supabase/auth-helpers-shared@0.1.1
180 |
181 | ## 0.6.2
182 |
183 | ### Patch Changes
184 |
185 | - d23b268: [breaking change] Update the getUser function to only get the user and not save the token
186 | - 1c95004: Add new handleAuth function to export all hooks as an array to destructure
187 | - 588d329: Add handleLogout hook for logging out via endpoint /api/auth/logout
188 |
189 | ## 0.6.1
190 |
191 | ### Patch Changes
192 |
193 | - 2f2abf2: Fix session being overwritten in SupaAuthHelper
194 |
195 | ## 0.6.0
196 |
197 | ### Minor Changes
198 |
199 | - 1e56b35: Add file extension to es modules imports
200 |
201 | ## 0.5.0
202 |
203 | ### Minor Changes
204 |
205 | - f854c7f: Remove unused helper files
206 |
207 | ## 0.4.0
208 |
209 | ### Minor Changes
210 |
211 | - 865cc4a: Update packages to compile to esm
212 |
213 | ## 0.3.0
214 |
215 | ### Minor Changes
216 |
217 | - f399820: Using shared package as a dependency
218 | Update sveltekit package with latest code to update tokens
219 |
220 | ## 0.2.0
221 |
222 | ### Minor Changes
223 |
224 | - 5d741ae: Remove svelte-preprocess from sveltekit package
225 |
226 | ## 0.1.0
227 |
228 | ### Minor Changes
229 |
230 | - de01c86: Initial release of svelte integration
231 |
--------------------------------------------------------------------------------