SvelteKit с Supabase SSR Auth Helpers


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, все комментарии, исправления или дополнения приветствуются.

Оцените статью
devanswers.ru
Добавить комментарий