This commit is contained in:
Copple
2022-08-10 11:39:03 +02:00
parent a66ede6fe3
commit ede7727eeb
2 changed files with 134 additions and 126 deletions
+65 -65
View File
@@ -20,6 +20,7 @@ npm install @supabase/auth-helpers-react
```
Using [yarn](https://yarnpkg.com/):
```sh
yarn add @supabase/auth-helpers-nextjs
@@ -54,9 +55,9 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
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';
import { handleAuth } from '@supabase/auth-helpers-nextjs'
export default handleAuth({ logout: { returnTo: '/' } });
export default handleAuth({ logout: { returnTo: '/' } })
```
Executing `handleAuth()` creates the following route handlers under the hood that perform different parts of the authentication flow:
@@ -71,16 +72,16 @@ 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';
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>
);
)
}
```
@@ -91,23 +92,23 @@ You can now determine if a user is authenticated by checking that the `user` obj
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';
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();
const { user, error } = useUser()
const [data, setData] = useState()
useEffect(() => {
async function loadData() {
const { data } = await supabaseClient.from('test').select('*');
setData(data);
const { data } = await supabaseClient.from('test').select('*')
setData(data)
}
// Only run query once user is logged in.
if (user) loadData();
}, [user]);
if (user) loadData()
}, [user])
if (!user)
return (
@@ -120,7 +121,7 @@ const LoginPage = () => {
socialButtonSize="xlarge"
/>
</>
);
)
return (
<>
@@ -130,10 +131,10 @@ const LoginPage = () => {
<p>client-side data fetching with RLS</p>
<pre>{JSON.stringify(data, null, 2)}</pre>
</>
);
};
)
}
export default LoginPage;
export default LoginPage
```
### Server-side rendering (SSR) - withPageAuth
@@ -142,13 +143,13 @@ If you wrap your `getServerSideProps` with `withPageAuth` your props object will
```js
// pages/profile.js
import { withPageAuth } from '@supabase/auth-helpers-nextjs';
import { withPageAuth } from '@supabase/auth-helpers-nextjs'
export default function Profile({ user }) {
return <div>Hello {user.name}</div>;
return <div>Hello {user.name}</div>
}
export const getServerSideProps = withPageAuth({ redirectTo: '/login' });
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.
@@ -158,20 +159,20 @@ user props. You can also access the user session data by calling `getUser` insid
```js
// pages/protected-page.js
import { withPageAuth, getUser } from '@supabase/auth-helpers-nextjs';
import { withPageAuth, getUser } from '@supabase/auth-helpers-nextjs'
export default function ProtectedPage({ user, customProp }) {
return <div>Protected content</div>;
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 } };
}
});
const { user, accessToken } = await getUser(ctx)
return { props: { email: user?.email } }
},
})
```
### Server-side data fetching with RLS
@@ -182,15 +183,15 @@ For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row
import {
User,
withPageAuth,
supabaseServerClient
} from '@supabase/auth-helpers-nextjs';
supabaseServerClient,
} from '@supabase/auth-helpers-nextjs'
export default function ProtectedPage({
user,
data
data,
}: {
user: User,
data: any
data: any,
}) {
return (
<>
@@ -198,17 +199,17 @@ export default function ProtectedPage({
<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 } };
}
});
const { data } = await supabaseServerClient(ctx).from('test').select('*')
return { props: { data } }
},
})
```
### Server-side data fetching to OAuth APIs using `provider_token`
@@ -216,7 +217,7 @@ export const getServerSideProps = withPageAuth({
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';
import { User, withPageAuth, getUser } from '@supabase/auth-helpers-nextjs'
interface Profile {
/* ... */
@@ -224,33 +225,33 @@ interface Profile {
export default function ProtectedPage({
user,
data
data,
}: {
user: User,
profile: Profile
profile: Profile,
}) {
return <div>Protected content</div>;
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'];
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 { 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}`
}
Authorization: `Bearer ${provider_token}`,
},
})
).json();
return { props: { profile } };
}
});
).json()
return { props: { profile } }
},
})
```
## Protecting API routes
@@ -262,16 +263,16 @@ Wrap an API Route to check that the user has a valid session. If they're not log
// pages/api/protected-route.js
import {
withApiAuth,
supabaseServerClient
} from '@supabase/auth-helpers-nextjs';
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);
});
.select('*')
res.json(data)
})
```
If you visit `/api/protected-route` without a valid session cookie, you will get a 401 response.
@@ -282,25 +283,24 @@ As an alternative to protecting individual pages using `getServerSideProps` with
```ts
// pages/protected/_middleware.ts
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs/middleware';
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs/middleware'
export const middleware = withMiddlewareAuth({ redirectTo: '/login' });
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';
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs/dist/middleware'
export const middleware = withMiddlewareAuth({
export const middleware = withMiddlewareAuth({
redirectTo: '/login',
authGuard: {
isPermitted: async (user) => user.email?.endsWith('@example.com') ?? false,
redirectTo: '/insufficient-permissions'
}
});
redirectTo: '/insufficient-permissions',
},
})
```
## Migrating from @supabase/supabase-auth-helpers to @supabase/auth-helpers
@@ -312,4 +312,4 @@ This is a step by step guide on migrating away from the `@supabase/supabase-auth
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`.
6. Uninstall `@supabase/supabase-auth-helpers`.
+69 -61
View File
@@ -20,6 +20,7 @@ npm install @supabase/auth-helpers-svelte
```
Using [yarn](https://yarnpkg.com/):
```sh
yarn add @supabase/auth-helpers-sveltekit
@@ -49,14 +50,14 @@ We will start off by creating a `db.ts` file inside of our `src/lib` directory.
```ts
// src/lib/db.ts
import { createSupabaseClient } from '@supabase/auth-helpers-sveltekit';
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 };
export { supabaseClient }
```
Edit your `__layout.svelte` file and add import the `SupaAuthHelper` component, the `supabaseClient` we just instantiated and the `session` store.
@@ -64,15 +65,15 @@ Edit your `__layout.svelte` file and add import the `SupaAuthHelper` component,
```html
// src/routes/__layout.svelte
<script>
import { session } from '$app/stores';
import { supabaseClient } from '$lib/db';
import { SupaAuthHelper } from '@supabase/auth-helpers-svelte';
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
@@ -80,18 +81,18 @@ Our `hooks.ts` file is where the heavy lifting of this library happens, we need
```ts
// src/hooks.ts
import { handleAuth } from '@supabase/auth-helpers-sveltekit';
import type { GetSession, Handle } from '@sveltejs/kit';
import { sequence } from '@sveltejs/kit/hooks';
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 handle: Handle = sequence(...handleAuth())
export const getSession: GetSession = async (event) => {
const { user, accessToken, error } = event.locals;
return {
user,
accessToken,
error
const { user, accessToken, error } = event.locals
return {
user,
accessToken,
error,
}
}
```
@@ -103,6 +104,7 @@ These will create the handlers under the hood that perform different parts of th
- `/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
@@ -111,19 +113,15 @@ In order to get the most out of TypeScript and its intellisense, you should impo
// 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 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 {}
interface Session extends UserSession {} // interface Platform {} // interface Stuff {}
}
```
@@ -144,9 +142,11 @@ In your `src/hooks.ts` file the logout handler is already setup and you can conf
> By default the redirect path after logging out will be `/`.
```ts
export const handle = sequence(...handleAuth({
logout: { returnTo: '/auth/signin' }
}));
export const handle = sequence(
...handleAuth({
logout: { returnTo: '/auth/signin' },
})
)
```
### Basic Setup
@@ -156,14 +156,14 @@ You can now determine if a user is authenticated on the client-side by checking
```html
// example
<script>
import { session } from '$app/stores';
import { session } from '$app/stores'
</script>
{#if !$session.user}
<h1>I am not logged in</h1>
<h1>I am not logged in</h1>
{:else}
<h1>Welcome {$session.user.email}</h1>
<p>I am logged in!</p>
<h1>Welcome {$session.user.email}</h1>
<p>I am logged in!</p>
{/if}
```
@@ -216,8 +216,8 @@ For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row
```html
<!-- src/routes/profile.svelte -->
<script>
export let user;
export let data;
export let user
export let data
</script>
<div>Protected content for {user.email}</div>
@@ -227,38 +227,41 @@ export let data;
```ts
// src/routes/profile.ts
import { supabaseServerClient, withApiAuth } from "@supabase/auth-helpers-sveltekit";
import type { RequestHandler } from "./__types/profile";
import {
supabaseServerClient,
withApiAuth,
} from '@supabase/auth-helpers-sveltekit'
import type { RequestHandler } from './__types/profile'
interface TestTable {
id: string;
created_at: string;
id: string
created_at: string
}
interface GetOutput {
user: User;
data: TestTable[];
user: User
data: TestTable[]
}
export const GET: RequestHandler<GetOutput> = async ({ locals }) =>
withApiAuth(
{
redirectTo: "/",
user: locals.user
redirectTo: '/',
user: locals.user,
},
async () => {
const { data } = await supabaseServerClient(session.accessToken)
.from<TestTable>("test")
.select("*");
.from<TestTable>('test')
.select('*')
return {
body: {
user: locals.user,
data
}
};
data,
},
}
}
);
)
```
## Protecting API routes
@@ -268,28 +271,33 @@ Wrap an API Route to check that the user has a valid session. If they're not log
```ts
// src/routes/api/protected-route.ts
import { supabaseServerClient, withApiAuth } from "@supabase/auth-helpers-sveltekit";
import type { RequestHandler } from "./__types/protected-route";
import {
supabaseServerClient,
withApiAuth,
} from '@supabase/auth-helpers-sveltekit'
import type { RequestHandler } from './__types/protected-route'
interface TestTable {
id: string;
created_at: string;
id: string
created_at: string
}
interface GetOutput {
data: TestTable[];
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("*");
const { data } = await supabaseServerClient(request)
.from('test')
.select('*')
return {
status: 200,
body: { data }
};
});
body: { data },
}
})
```
If you visit `/api/protected-route` without a valid session cookie, you will get a 303 response.
If you visit `/api/protected-route` without a valid session cookie, you will get a 303 response.