Adds the Auth helpers

This commit is contained in:
Copple committed 2022-08-08 13:28:10 +02:00
1 parent 73670c6cbd
commit beaf7f830f
7 files changed
+687 -6

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
---
slug: /
sidebar_label: Auth Helpers
---
# Auth Helpers
A collection of framework specific Auth utilities for working with Supabase.
+315
View File
@@ -0,0 +1,315 @@
---
id: next-js
slug: next-js
sidebar_label: With Next.js
---
# Supabase Auth with Next.js
This submodule provides convenience helpers for implementing user authentication in Next.js applications.
## Installation
Using [npm](https://npmjs.org):
```sh
npm install @supabase/auth-helpers-nextjs
# Main components and hooks for React based frameworks (optional)
npm install @supabase/auth-helpers-react
```
Using [yarn](https://yarnpkg.com/):
```sh
yarn add @supabase/auth-helpers-nextjs
# Main components and hooks for React based frameworks (optional)
yarn add @supabase/auth-helpers-react
```
This library supports the following tooling versions:
- Node.js: `^10.13.0 || >=12.0.0`
- Next.js: `>=10`
## Getting Started
### Configuration
Set up the fillowing env vars. For local development you can set them in a `.env.local` file. See an example [here](../../examples/nextjs/.env.local.example)).
```bash
# Find these in your Supabase project settings > API
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
```
### Basic Setup
- Create an `auth` directory under the `/pages/api/` directory.
- Create a `[...supabase].js` file under the newly created `auth` directory.
The path to your dynamic API route file would be `/pages/api/auth/[...supabase].js`. Populate that file as follows:
```js
import { handleAuth } from '@supabase/auth-helpers-nextjs';
export default handleAuth({ logout: { returnTo: '/' } });
```
Executing `handleAuth()` creates the following route handlers under the hood that perform different parts of the authentication flow:
- `/api/auth/callback`: The `UserProvider` forwards the session details here every time `onAuthStateChange` fires on the client side. This is needed to set up the cookies for your application so that SSR works seamlessly.
- `/api/auth/user`: You can fetch user profile information in JSON format.
- `/api/auth/logout`: Your Next.js application logs out the user. You can optionally pass a `returnTo` parameter to return to a custom relative URL after logout, eg `/api/auth/logout?returnTo=/login`. This will overwrite the logout `returnTo` option specified `handleAuth()`
Wrap your `pages/_app.js` component with the `UserProvider` component:
```jsx
// pages/_app.js
import React from 'react';
import { UserProvider } from '@supabase/auth-helpers-react';
import { supabaseClient } from '@supabase/auth-helpers-nextjs';
export default function App({ Component, pageProps }) {
return (
<UserProvider supabaseClient={supabaseClient}>
<Component {...pageProps} />
</UserProvider>
);
}
```
You can now determine if a user is authenticated by checking that the `user` object returned by the `useUser()` hook is defined.
## Client-side data fetching with RLS
For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security) to work properly when fetching data client-side, you need to make sure to import the `{ supabaseClient }` from `# @supabase/auth-helpers-nextjs` and only run your query once the user is defined client-side in the `useUser()` hook:
```js
import { Auth } from '@supabase/ui';
import { useUser } from '@supabase/auth-helpers-react';
import { supabaseClient } from '@supabase/auth-helpers-nextjs';
import { useEffect, useState } from 'react';
const LoginPage = () => {
const { user, error } = useUser();
const [data, setData] = useState();
useEffect(() => {
async function loadData() {
const { data } = await supabaseClient.from('test').select('*');
setData(data);
}
// Only run query once user is logged in.
if (user) loadData();
}, [user]);
if (!user)
return (
<>
{error && <p>{error.message}</p>}
<Auth
supabaseClient={supabaseClient}
providers={['google', 'github']}
socialLayout="horizontal"
socialButtonSize="xlarge"
/>
</>
);
return (
<>
<button onClick={() => supabaseClient.auth.signOut()}>Sign out</button>
<p>user:</p>
<pre>{JSON.stringify(user, null, 2)}</pre>
<p>client-side data fetching with RLS</p>
<pre>{JSON.stringify(data, null, 2)}</pre>
</>
);
};
export default LoginPage;
```
### Server-side rendering (SSR) - withPageAuth
If you wrap your `getServerSideProps` with `withPageAuth` your props object will be augmented with the user object.
```js
// pages/profile.js
import { withPageAuth } from '@supabase/auth-helpers-nextjs';
export default function Profile({ user }) {
return <div>Hello {user.name}</div>;
}
export const getServerSideProps = withPageAuth({ redirectTo: '/login' });
```
If there is no authenticated user, they will be redirect to your home page, unless you specify the `redirectTo` option.
You can pass in your own `getServerSideProps` method, the props returned from this will be merged with the
user props. You can also access the user session data by calling `getUser` inside of this method, eg:
```js
// pages/protected-page.js
import { withPageAuth, getUser } from '@supabase/auth-helpers-nextjs';
export default function ProtectedPage({ user, customProp }) {
return <div>Protected content</div>;
}
export const getServerSideProps = withPageAuth({
redirectTo: '/foo',
async getServerSideProps(ctx) {
// Access the user object
const { user, accessToken } = await getUser(ctx);
return { props: { email: user?.email } };
}
});
```
### Server-side data fetching with RLS
For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security) to work in a server environment, you need to inject the request context into the supabase client:
```js
import {
User,
withPageAuth,
supabaseServerClient
} from '@supabase/auth-helpers-nextjs';
export default function ProtectedPage({
user,
data
}: {
user: User,
data: any
}) {
return (
<>
<div>Protected content for {user.email}</div>
<pre>{JSON.stringify(data, null, 2)}</pre>
<pre>{JSON.stringify(user, null, 2)}</pre>
</>
);
}
export const getServerSideProps = withPageAuth({
redirectTo: '/',
async getServerSideProps(ctx) {
// Run queries with RLS on the server
const { data } = await supabaseServerClient(ctx).from('test').select('*');
return { props: { data } };
}
});
```
### Server-side data fetching to OAuth APIs using `provider_token`
When using third-party auth providers, sessions are initiated with an additional `provider_token` field which is persisted as an HTTPOnly cookie upon logging in to enabled usage on the server side. The `provider_token` can be used to make API requests to the OAuth provider's API endpoints on behalf of the logged-in user. In the following example, we fetch the user's full profile from the third-party API during SSR using their id and auth token:
```js
import { User, withPageAuth, getUser } from '@supabase/auth-helpers-nextjs';
interface Profile {
/* ... */
}
export default function ProtectedPage({
user,
data
}: {
user: User,
profile: Profile
}) {
return <div>Protected content</div>;
}
export const getServerSideProps = withPageAuth({
redirectTo: '/',
async getServerSideProps(ctx) {
// Retrieve provider_token from cookies
const provider_token = ctx.req.cookies['sb-provider-token'];
// Get logged in user's third-party id from metadata
const { user } = await getUser(ctx);
const userId = user?.user_metadata.provider_id;
const profile: Profile = await (
await fetch(`https://api.example.com/users/${userId}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${provider_token}`
}
})
).json();
return { props: { profile } };
}
});
```
## Protecting API routes
Wrap an API Route to check that the user has a valid session. If they're not logged in the handler will return a
401 Unauthorized.
```js
// pages/api/protected-route.js
import {
withApiAuth,
supabaseServerClient
} from '@supabase/auth-helpers-nextjs';
export default withApiAuth(async function ProtectedRoute(req, res) {
// Run queries with RLS on the server
const { data } = await supabaseServerClient({ req, res })
.from('test')
.select('*');
res.json(data);
});
```
If you visit `/api/protected-route` without a valid session cookie, you will get a 401 response.
## Protecting routes with [Nextjs Middleware](https://nextjs.org/docs/middleware)
As an alternative to protecting individual pages using `getServerSideProps` with `withPageAuth`, `withMiddlewareAuth` can be used from inside a `_middleware` file to protect an entire directory. In the following example, all requests to `/protected/*` will check whether a user is signed in, if successful the request will be forwarded to the destination route, otherwise the user will be redirected to `/login` (defaults to: `/`) with a 307 Temporary Redirect response status:
```ts
// pages/protected/_middleware.ts
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs/middleware';
export const middleware = withMiddlewareAuth({ redirectTo: '/login' });
```
It is also possible to add finer granularity based on the user logged in. I.e. you can specify a promise to determine if a specific user has permission or not.
```ts
// pages/protected/_middleware.ts
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs/dist/middleware';
export const middleware = withMiddlewareAuth({
redirectTo: '/login',
authGuard: {
isPermitted: async (user) => user.email?.endsWith('@example.com') ?? false,
redirectTo: '/insufficient-permissions'
}
});
```
## Migrating from @supabase/supabase-auth-helpers to @supabase/auth-helpers
This is a step by step guide on migrating away from the `@supabase/supabase-auth-helpers` to the newly released `@supabase/auth-helpers`.
1. Install `@supabase/supabase-js`, `@supabase/auth-helpers-nextjs` and `@supabase/auth-helpers-react` libraries from npm.
2. Replace all imports of `@supabase/supabase-auth-helpers/nextjs` in your project with `@supabase/auth-helpers-nextjs`.
3. Replace all imports of `@supabase/supabase-auth-helpers/react` in your project with `@supabase/auth-helpers-react`.
4. Replace all instances of `withAuthRequired` in any of your NextJS pages with `withPageAuth`.
5. Replace all instances of `withAuthRequired` in any of your NextJS API endpoints with `withApiAuth`.
6. Uninstall `@supabase/supabase-auth-helpers`.
+295
View File
@@ -0,0 +1,295 @@
---
id: sveltekit
slug: sveltekit
sidebar_label: With SvelteKit
---
# Supabase Auth with SvelteKit
This submodule provides convenience helpers for implementing user authentication in [SvelteKit](https://kit.svelte.dev/) applications.
## Installation
Using [npm](https://npmjs.org):
```sh
npm install @supabase/auth-helpers-sveltekit
# Main component for Svelte based frameworks (optional but recommended)
npm install @supabase/auth-helpers-svelte
```
Using [yarn](https://yarnpkg.com/):
```sh
yarn add @supabase/auth-helpers-sveltekit
# Main component for Svelte based frameworks (optional but recommended)
yarn add @supabase/auth-helpers-svelte
```
This library supports the following tooling versions:
- Node.js: `^16.15.0`
## Getting Started
### Configuration
Set up the fillowing env vars. For local development you can set them in a `.env` file. See an example [here](../../examples/sveltekit/.env.example).
```bash
# Find these in your Supabase project settings > API
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key
```
### SupabaseClient and SupaAuthHelper component setup
We will start off by creating a `db.ts` file inside of our `src/lib` directory. Now lets instantiate our `supabaseClient` by using our `createSupabaseClient` function from the `@supabase/auth-helpers-sveltekit` library.
```ts
// src/lib/db.ts
import { createSupabaseClient } from '@supabase/auth-helpers-sveltekit';
const { supabaseClient } = createSupabaseClient(
import.meta.env.VITE_SUPABASE_URL as string,
import.meta.env.VITE_SUPABASE_ANON_KEY as string
);
export { supabaseClient };
```
Edit your `__layout.svelte` file and add import the `SupaAuthHelper` component, the `supabaseClient` we just instantiated and the `session` store.
```html
// src/routes/__layout.svelte
<script>
import { session } from '$app/stores';
import { supabaseClient } from '$lib/db';
import { SupaAuthHelper } from '@supabase/auth-helpers-svelte';
</script>
<SupaAuthHelper {supabaseClient} {session}>
<slot />
</SupaAuthHelper>
````
### Hooks setup
Our `hooks.ts` file is where the heavy lifting of this library happens, we need to import our function to handle the sign in, signing out and cookie creation phase. we can import all the hooks using `handleAuth` function and destructure its returned data.
```ts
// src/hooks.ts
import { handleAuth } from '@supabase/auth-helpers-sveltekit';
import type { GetSession, Handle } from '@sveltejs/kit';
import { sequence } from '@sveltejs/kit/hooks';
export const handle: Handle = sequence(...handleAuth());
export const getSession: GetSession = async (event) => {
const { user, accessToken, error } = event.locals;
return {
user,
accessToken,
error
}
}
```
These will create the handlers under the hood that perform different parts of the authentication flow:
- `/api/auth/callback`: The `UserHelper` forwards the session details here every time `onAuthStateChange` fires on the client side. This is needed to set up the cookies for your application so that SSR works seamlessly.
- `/api/auth/user`: You can fetch user profile information in JSON format.
- `/api/auth/logout`: You can logout the user.
### Typings
In order to get the most out of TypeScript and its intellisense, you should import our types into the `app.d.ts` type definition file that comes with your SvelteKit project.
```ts
// src/app.d.ts
/// <reference types="@sveltejs/kit" />
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare namespace App {
  interface UserSession {
    user: import('@supabase/supabase-js').User;
    accessToken?: string;
  }
 
  interface Locals extends UserSession {
    error: import('@supabase/supabase-js').ApiError;
  }
  interface Session extends UserSession {}
  // interface Platform {}
  // interface Stuff {}
}
```
### Signing out
This library has provided a dedicated endpoint for you to use to sign a user out. This endpoint will sign the user out of the Gotrue server, clear the cookies that were set when the user logged in and redirect the user to a configurable path.
The logout handler endpoint is `/api/auth/logout`, this will take a `GET` request which means it can be used as the href for a normal `a` tag in your html.
```html
<a href="/api/auth/logout">Sign out</a>
```
### Logout handler configuration
In your `src/hooks.ts` file the logout handler is already setup and you can configure the redirect path from here.
> By default the redirect path after logging out will be `/`.
```ts
export const handle = sequence(...handleAuth({
logout: { returnTo: '/auth/signin' }
}));
```
### Basic Setup
You can now determine if a user is authenticated on the client-side by checking that the `user` object returned by the `$session` store is defined.
```html
// example
<script>
import { session } from '$app/stores';
</script>
{#if !$session.user}
<h1>I am not logged in</h1>
{:else}
<h1>Welcome {$session.user.email}</h1>
<p>I am logged in!</p>
{/if}
```
## Client-side data fetching with RLS
For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security) to work properly when fetching data client-side, you need to make sure to import the `{ supabaseClient }` from `@supabase/auth-helpers-sveltekit` and only run your query once the user is defined client-side in the `$session`:
```html
<script>
import Auth from 'supabase-ui-svelte';
import { error, isLoading } from '@supabase/auth-helpers-svelte';
import { supabaseClient } from '$lib/db';
import { session } from '$app/stores';
let loadedData = [];
async function loadData() {
const { data } = await supabaseClient.from('test').select('*').single();
loadedData = data
}
$: {
if ($session.user && $session.user.id) {
loadData();
}
}
</script>
{#if !$session.user}
{#if $error}
<p>{$error.message}</p>
{/if}
<h1>{$isLoading ? `Loading...` : `Loaded!`}</h1>
<Auth
supabaseClient={supabaseClient}
providers={['google', 'github']}
/>
{:else}
<a href=="/api/auth/logout">Sign out</a>
<p>user:</p>
<pre>{JSON.stringify($session.user, null, 2)}</pre>
<p>client-side data fetching with RLS</p>
<pre>{JSON.stringify(loadedData, null, 2)}</pre>
{/if}
```
### Server-side data fetching with RLS
For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security) to work in a server environment, you need to inject the request context into the supabase client:
```html
<!-- src/routes/profile.svelte -->
<script>
export let user;
export let data;
</script>
<div>Protected content for {user.email}</div>
<pre>{JSON.stringify(data, null, 2)}</pre>
<pre>{JSON.stringify(user, null, 2)}</pre>
```
```ts
// src/routes/profile.ts
import { supabaseServerClient, withApiAuth } from "@supabase/auth-helpers-sveltekit";
import type { RequestHandler } from "./__types/profile";
interface TestTable {
id: string;
created_at: string;
}
interface GetOutput {
user: User;
data: TestTable[];
}
export const GET: RequestHandler<GetOutput> = async ({ locals }) =>
withApiAuth(
{
redirectTo: "/",
user: locals.user
},
async () => {
const { data } = await supabaseServerClient(session.accessToken)
.from<TestTable>("test")
.select("*");
return {
body: {
user: locals.user,
data
}
};
}
);
```
## Protecting API routes
Wrap an API Route to check that the user has a valid session. If they're not logged in the handler will return a
303 and redirect header.
```ts
// src/routes/api/protected-route.ts
import { supabaseServerClient, withApiAuth } from "@supabase/auth-helpers-sveltekit";
import type { RequestHandler } from "./__types/protected-route";
interface TestTable {
id: string;
created_at: string;
}
interface GetOutput {
data: TestTable[];
}
export const GET: RequestHandler<GetOutput> = async ({ locals, request }) =>
withApiAuth({ user: locals.user }, async () => {
// Run queries with RLS on the server
const { data } = await supabaseServerClient(request).from("test").select("*");
return {
status: 200,
body: { data }
};
});
```
If you visit `/api/protected-route` without a valid session cookie, you will get a 303 response.
+10
View File
@@ -89,6 +89,16 @@ const config = {
breadcrumbs: false,
},
],
[
'@docusaurus/plugin-content-docs',
{
id: '_auth_helpers',
path: '_auth_helpers',
routeBasePath: 'auth-helpers',
sidebarPath: require.resolve('./nav/auth_helpers_sidebars.js'),
breadcrumbs: false,
},
],
],
presets: [
+13
View File
@@ -121,6 +121,19 @@ const navbar = [
// docsPluginId: '_supabase_dart',
// supabaseCustomNavBarRegex: '(^/supabase-dart$|supabase-dart/)',
// },
{
to: 'auth-helpers',
position: 'left',
label: 'Auth Helpers',
supabaseCustomNavBarRegex: '(^/auth-helpers$|auth-helpers/)',
},
// {
// type: 'docsVersionDropdown',
// position: 'left',
// docsPluginId: '_auth_helpers',
// supabaseCustomNavBarRegex: '(^/auth-helpers$|auth-helpers/)',
// },
]
module.exports = { navbar }
+12 -6
View File
@@ -34,12 +34,18 @@ const sidebars = {
},
],
},
// {
// type: 'category',
// label: 'Community',
// collapsed: false,
// items: [],
// },
{
type: 'category',
label: 'Community',
collapsed: false,
items: [
{
type: 'link',
label: 'Supabase Auth Helpers',
href: '/auth-helpers',
},
],
},
{
type: 'category',
label: 'Self hosted',
@@ -0,0 +1,34 @@
/**
* Creating a sidebar enables you to:
- create an ordered group of docs
- render a sidebar for each doc of that group
- provide next/previous navigation
The sidebars can be generated from the filesystem, or explicitly defined here.
Create as many sidebars as you want.
*/
// @ts-check
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
// By default, Docusaurus generates a sidebar from the docs folder structure
// tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }],
sidebar: [
'intro',
'next-js',
'sveltekit',
// 'usage',
// 'config',
// 'release-notes',
// {
// type: "category",
// label: "Release Notes",
// items: ["release-notes"],
// },
],
}
module.exports = sidebars