- TL;DR
- Что такое помощники авторизации?
- Как использовать Supabase Auth Helpers в SvelteKit Projet?
- Подробное руководство
- Настройка проекта SvelteKit
- Установите помощники Supabase Auth для SvelteKit
- Настройка проекта Supabase
- Создание клиента Supabase
- Хуки
- Макет
- Страница регистрации и маршрут
- Страница входа в систему (индекс) и маршрут
- Страница перенаправления подтверждения электронной почты
- Страница приборной панели и маршрут — страница только для защищенных пользователей
- Макет перенаправления подтверждения электронной почты
TL;DR
https://github.com/kvetoslavnovak/SvelteKitSupabaseAuthApp
Что такое помощники авторизации?
Компания Supabase недавно объявила об обновлении помощников авторизации для Supabase с поддержкой SvelteKit.
По словам представителей Supabase, одной из проблем было создание простого опыта для сред рендеринга на стороне сервера (SSR). Auth Helpers — это набор специфичных для фреймворка утилит для Supabase Auth. Они позволяют реализовать безопасные приложения без особых усилий. Эти библиотеки включают функции для защиты маршрутов API и страниц в ваших приложениях.
Как использовать Supabase Auth Helpers в SvelteKit Projet?
Просто установите эти два пакета npm в свой проект SvelteKit:
npm install @supabase/auth-helpers-sveltekit
npm install @supabase/auth-helpers-svelte
Подробное руководство
Вот моя копия учебника на github.
Настройка проекта SvelteKit
Мы постараемся создать минимальный проект на голом энтузиазме. Вы можете добавить любую стилизацию, проверку типов и т.д. позже. Supabase опубликовал довольно хорошие примеры с использованием TypeScript и документацию.
Назовем проект SvelteKitSupabaseAuthApp.
Используйте только скелетный проект, без TypeScript, без ESLint, без Prettier, без Playwright.
npm create svelte@latest SvelteKitSupabaseAuthApp
cd SvelteKitSupabaseAuthApp
npm install
Установите помощники Supabase Auth для SvelteKit
npm install @supabase/auth-helpers-sveltekit
npm install @supabase/auth-helpers-svelte
Настройка проекта Supabase
- Войдите в Supabase и создайте новый проект на приборной панели Supabase.
- Перейдите в раздел Authentication > Settings и измените User Sessions — Site URL с http://localhost:3000 на http://localhost:5173 и сохраните. Это адрес localhost, по которому SvelteKit обслуживает проект в режиме разработки. Это изменение было введено в Vite 3.
- Перейдите в раздел Settings > API и получите URL и anon ключ вашего проекта.
- Вернитесь к проекту SvelteKitSupabaseAuthApp и в его корне создайте новый файл .env. Замените VITE_SUPABASE_URL на URL из предыдущего шага, а VITE_SUPABASE_ANON_KEY на anon ключ из предыдущего шага:
# .env
# Update these with your Supabase details from your project settings > API
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key
Создание клиента Supabase
В папке src создайте новую папку lib и в этой папке lib создайте новый файл sb.js, здесь мы создадим supabaseClient, используя наши учетные данные из .env end экспортируем supabaseClient, чтобы мы могли использовать его методы авторизации в нашем приложении.
В этом приложении мы будем использовать вход с помощью электронной почты. Supabase автоматически отправляет подтверждение по электронной почте, когда пользователь регистрируется.
// lib/sb.js
import { createSupabaseClient } from '@supabase/auth-helpers-sveltekit';
const { supabaseClient } = createSupabaseClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY
);
export { supabaseClient };
Хуки
В SvelteKit хуки позволяют нам использовать две важные функции, функцию handle и функцию getSession. Функция handle запускается каждый раз, когда сервер SvelteKit получает запрос. Функция getSession принимает объект события и возвращает объект сессии, доступный на клиенте. Это позволяет нам, например, получить доступ к данным о пользователе и cookies.
В папке src создайте новый файл hooks.js
// src/hooks.js
import { handleAuth } from '@supabase/auth-helpers-sveltekit';
import { sequence } from '@sveltejs/kit/hooks';
export const handle = sequence(
...handleAuth({
cookieOptions: { lifetime: 1 * 365 * 24 * 60 * 60 }
})
);
export const getSession = async (event) => {
const { user, accessToken } = event.locals;
return {
user,
accessToken
};
};
Макет
В папке src/routes/ создайте новый файл макета __layout.svelte, чтобы мы могли иметь простой макет, но, что более важно, чтобы представить наш компонент SupaAuthHelper.
// src/routes/__layout.svelte
<script>
import { session } from '$app/stores';
import { supabaseClient } from '$lib/sb';
import { SupaAuthHelper } from '@supabase/auth-helpers-svelte';
</script>
<svelte:head>
<title>Email and Password Demo - Supabase Auth Helpers</title>
</svelte:head>
<SupaAuthHelper {supabaseClient} {session}>
<main>
<div>
<h2>
<a href="/">Supabase Auth Helpers Demo</a>
</h2>
<slot />
<div>
{#if $session?.user?.id}
<a href="/api/auth/logout">Sign out</a>
{/if}
</div>
</div>
</main>
</SupaAuthHelper>
Страница регистрации и маршрут
В папке src/routes/ создайте новый файл signup,svelte Просто простая форма регистрации.
// src/routes/signup,svelte
<script>
export let errors = null;
export let values = null;
export let message = null;
</script>
<section>
<div>
<h1>Sign up</h1>
{#if errors}
<div>{errors.form}</div>
{/if}
{#if message}
<div>{message}</div>
{/if}
<form method="post">
<div>
<label for="email">Email</label>
<p>
<input
id="email"
name="email"
value={values?.email ?? ''}
type="email"
placeholder="Email"
required
/>
</p>
</div>
<div>
<label for="password">Password</label>
<p>
<input
id="password"
name="password"
value={values?.password ?? ''}
type="password"
placeholder="Password"
required
/>
</p>
</div>
<div>
<p>
<button>Sign up</button>
</p>
</div>
</form>
<div>
<p>
Already have an account? <a href="/">Sign in</a>
</p>
</div>
</div>
</section>
В папке src/routes/ создайте новый файл signup.js. Эта конечная точка направляет существующего пользователя на /dashboard, которая является защищенной страницей только для пользователей, или предоставляет данные для регистрации в supabaseClient. Мы также добавляем объект, определяющий перенаправление, когда клиент нажимает на ссылку подтверждения в письме с подтверждением, которое Supabase отправит ему.
// src/routes/signup.js
import { supabaseClient } from '$lib/sb';
export const GET = async ({ locals }) => {
// if the user is already logged in, then redirect to the dashboard
if (locals.user) {
return {
status: 303,
headers: {
location: '/dashboard'
}
};
}
return {
status: 200
};
};
export const POST = async ({ request, url }) => {
const data = await request.formData();
const email = data.get('email');
const password = data.get('password');
const errors = {};
const values = { email, password };
const { error } = await supabaseClient.auth.signUp(
{ email, password },
{ redirectTo: `${url.origin}/logging-in`}
);
if (error) {
errors.form = error.message;
return {
status: 400,
body: {
errors,
values
}
};
}
return {
status: 200,
body: {
message: 'Please check your email for a confirmation email.'
}
};
};
Страница входа в систему (индекс) и маршрут
В папке src/routes/ замените index.svelte на этот новый. Это простая форма входа в систему.
// src/routes/index.svelte
<script>
export let errors;
export let values;
</script>
<section>
<div>
<h1>Sign in</h1>
{#if errors}
<div>{errors.form}</div>
{/if}
<form method="post">
<div>
<label for="email">Email</label>
<p>
<input
id="email"
name="email"
value={values?.email ?? ''}
type="email"
placeholder="Email"
required
/>
</p>
</div>
<div>
<label for="password">Password</label>
<p>
<input
id="password"
name="password"
value={values?.password ?? ''}
type="password"
placeholder="Password"
required
/>
</p>
</div>
<div>
<p>
<button>Sign in</button>
</p>
</div>
</form>
<div>
<p>
Don't have an account? <a href="/signup">Sign up</a>
</p>
</div>
</div>
</section>
В папке src/routes/ создайте новый файл index.js. Эта конечная точка направляет существующего пользователя на /dashboard, которая является защищенной страницей только для пользователей, или предоставляет данные для входа в supabaseClient, возвращенная сессия используется для создания cookie.
//src/routes/index.js
import { supabaseClient } from '$lib/sb';
export const GET = async ({ locals }) => {
if (locals.user) {
return {
status: 303,
headers: {
location: '/dashboard'
}
};
}
return {
status: 200
};
};
export const POST = async ({ request, url }) => {
const data = await request.formData();
const email = data.get('email');
const password = data.get('password');
const headers = { location: '/dashboard' };
const errors = {};
const values = { email, password };
const { session, error } = await supabaseClient.auth.signIn({ email, password });
if (error) {
errors.form = error.message;
return {
status: 400,
body: {
errors,
values
}
};
}
if (session) {
const response = await fetch(`${url.origin}/api/auth/callback`, {
method: 'POST',
headers: new Headers({ 'Content-Type': 'application/json' }),
credentials: 'same-origin',
body: JSON.stringify({ event: 'SIGNED_IN', session })
});
// TODO: Add helper inside of auth-helpers-sveltekit library to manage this better
const cookies = response.headers
.get('set-cookie')
.split('SameSite=Lax, ')
.map((cookie) => {
if (!cookie.includes('SameSite=Lax')) {
cookie += 'SameSite=Lax';
}
return cookie;
});
headers['Set-Cookie'] = cookies;
}
return {
status: 303,
headers
};
};
Страница перенаправления подтверждения электронной почты
В папке src/routes/ создайте новый файл logging-in@blank.svelte, чтобы мы могли проверить, перенаправляется ли пользователь после подтверждения электронной почты, которая уже была установлена в хранилище сессий. Позже мы также предоставим специальный макет для этого случая. Во время моих тестов редирект и хранилище сессий работали довольно быстро, поэтому эта страница может быть видна довольно редко.
src/routes/logging-in@blank.svelte
<script>
import { session } from '$app/stores';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
let redirectPath = '/dashboard';
$: {
const redirectTo = $page.url.searchParams.get('redirect');
if (redirectTo) {
redirectPath = redirectTo;
}
// check if user has been set in session store then redirect
if ($session?.user?.id) {
goto(redirectPath);
}
}
</script>
<section>
<div>
<progress class="progress" max="100" />
</div>
<div>
Signing in from the email confirmation link ...
</div>
</section>
<style>
.progress:indeterminate {
animation-duration: 3.8s;
}
</style>
Страница приборной панели и маршрут — страница только для защищенных пользователей
В папке src/routes/ создайте новый файл dashboard.svelte, в котором мы отобразим некоторые данные о пользователе, хранящиеся в Supabase,
src/routes/dashboard.svelte
<script>
export let user;
</script>
<div>
<h3>This is protected route accesible only by logged users</h3>
<p>Hello user {user.email}</p>
</div>
<div>
<p>User {user.email} details:</p>
<pre>{JSON.stringify(user, null, 2)}</pre>
</div>
В папке src/routes/ создайте новый файл dashboard.js.
src/routes/dashboard.js
import { supabaseServerClient, withApiAuth } from '@supabase/auth-helpers-sveltekit';
export const GET = async ({ locals, request }) =>
withApiAuth(
{
redirectTo: '/',
user: locals.user
},
async () => {
// const { data } = await supabaseServerClient(request).from('test').select('*');
return {
body: {
// data,
user: locals.user
}
};
}
);
Макет перенаправления подтверждения электронной почты
В папке src/routes/ создайте новый файл __layout-blank.svelte, в котором мы создадим специальный макет для перенаправления подтверждения электронной почты, как упоминалось ранее.
//src/routes/__layout-blank.svelte
<script>
import { session } from '$app/stores';
import { supabaseClient } from '$lib/sb';
import { SupaAuthHelper } from '@supabase/auth-helpers-svelte';
</script>
<svelte:head>
<title>Email and Password Demo - Supabase Auth Helpers</title>
</svelte:head>
<SupaAuthHelper {supabaseClient} {session}>
<slot />
</SupaAuthHelper>
Надеюсь, этот урок был полезен. Большое спасибо ребятам из Supabase за SvelteKit Auth Helpers, их примеры стали настоящим источником для этой статьи. Поскольку я не являюсь экспертом в области авторизации, особенно в отношении SSR, все комментарии, исправления или дополнения приветствуются.