Пропустить до содержимого

Supabase и Astro

Supabase — это альтернатива Firebase с открытым исходным кодом. Она предоставляет базу данных Postgres, аутентификацию, edge-функции, подписки в реальном времени и хранилище.

  • Проект Supabase. Если у вас его нет, вы можете бесплатно зарегистрироваться на сайте supabase.com и создать новый проект.
  • Проект Astro с включенным серверным рендерингом (SSR).
  • Учетные данные Supabase для вашего проекта. Вы можете найти их на вкладке Settings > API вашего проекта Supabase.
    • SUPABASE_URL: URL-адрес вашего проекта Supabase.
    • SUPABASE_ANON_KEY: Анонимный ключ для вашего проекта Supabase.

Чтобы добавить учетные данные Supabase в проект Astro, добавьте следующее в файл .env:

.env
SUPABASE_URL=ВАШ_SUPABASE_URL
SUPABASE_ANON_KEY=ВАШ_SUPABASE_ANON_КЛЮЧ

Теперь эти переменные окружения доступны в вашем проекте.

Если вы хотите иметь IntelliSense для ваших переменных окружения, отредактируйте или создайте env.d.ts в вашем каталоге src/ и добавьте следующее:

src/env.d.ts
interface ImportMetaEnv {
readonly SUPABASE_URL: string
readonly SUPABASE_ANON_KEY: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}

Теперь ваш проект должен включать эти файлы:

  • Directorysrc/
    • env.d.ts
  • .env
  • astro.config.mjs
  • package.json

Чтобы подключиться к Supabase, вам необходимо установить @supabase/supabase-js в ваш проект.

Terminal window
npm install @supabase/supabase-js

Далее создайте папку с именем lib в каталоге src/. В нее вы добавите клиент Supabase.

В supabase.ts добавьте следующее для инициализации клиента Supabase:

src/lib/supabase.ts
import { createClient } from "@supabase/supabase-js";
export const supabase = createClient(
import.meta.env.SUPABASE_URL,
import.meta.env.SUPABASE_ANON_KEY,
);

Теперь ваш проект должен включать эти файлы:

  • Directorysrc/
    • Directorylib/
      • supabase.ts
    • env.d.ts
  • .env
  • astro.config.mjs
  • package.json

Добавление аутентификации с помощью Supabase

Заголовок раздела Добавление аутентификации с помощью Supabase

Supabase обеспечивает аутентификацию из коробки. Она поддерживает аутентификацию по электронной почте/паролю и аутентификацию по протоколу OAuth со многими провайдерами, включая GitHub, Google и некоторые другие.

  • Проект Astro инициализированный с Supabase.
  • Проект Supabase с включенной аутентификацией по электронной почте/паролю. Включить эту функцию можно на вкладке Authentication > Providers проекта Supabase.

Создание конечных точек сервера аутентификации

Заголовок раздела Создание конечных точек сервера аутентификации

Чтобы добавить аутентификацию в ваш проект, вам нужно создать несколько конечных точек сервера. Эти конечные точки будут использоваться для регистрации, входа и выхода пользователей.

  • POST /api/auth/register: для регистрации нового пользователя.
  • POST /api/auth/signin: для авторизации пользователя.
  • GET /api/auth/signout: для выхода пользователя из системы.

Создайте эти конечные точки в директории src/pages/api/auth вашего проекта. Теперь ваш проект должен включать эти новые файлы:

  • Directorysrc/
    • Directorylib/
      • supabase.ts
    • Directorypages/
      • Directoryapi/
        • Directoryauth/
          • signin.ts
          • signout.ts
          • register.ts
    • env.d.ts
  • .env
  • astro.config.mjs
  • package.json

register.ts создает нового пользователя в Supabase. Он принимает запрос POST с указанием электронной почты и пароля. Затем он использует Supabase SDK для создания нового пользователя.

src/pages/api/auth/register.ts
import type { APIRoute } from "astro";
import { supabase } from "../../../lib/supabase";
export const POST: APIRoute = async ({ request, redirect }) => {
const formData = await request.formData();
const email = formData.get("email")?.toString();
const password = formData.get("password")?.toString();
if (!email || !password) {
return new Response("Требуется ввести email и пароль", { status: 400 });
}
const { error } = await supabase.auth.signUp({
email,
password,
});
if (error) {
return new Response(error.message, { status: 500 });
}
return redirect("/signin");
};

signin.ts авторизирует пользователя. Он принимает запрос POST с указанием электронной почты и пароля. Затем он использует Supabase SDK для регистрации пользователя.

src/pages/api/auth/signin.ts
import type { APIRoute } from "astro";
import { supabase } from "../../../lib/supabase";
export const POST: APIRoute = async ({ request, cookies, redirect }) => {
const formData = await request.formData();
const email = formData.get("email")?.toString();
const password = formData.get("password")?.toString();
if (!email || !password) {
return new Response("Требуется ввести email и пароль", { status: 400 });
}
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
return new Response(error.message, { status: 500 });
}
const { access_token, refresh_token } = data.session;
cookies.set("sb-access-token", access_token, {
path: "/",
});
cookies.set("sb-refresh-token", refresh_token, {
path: "/",
});
return redirect("/dashboard");
};

В signout.ts происходит выход пользователя из системы. Он принимает запрос GET и удаляет токены доступа и обновления пользователя.

src/pages/api/auth/signout.ts
import type { APIRoute } from "astro";
export const GET: APIRoute = async ({ cookies, redirect }) => {
cookies.delete("sb-access-token", { path: "/" });
cookies.delete("sb-refresh-token", { path: "/" });
return redirect("/signin");
};

Теперь, когда вы создали конечные точки сервера, создайте страницы, которые будут их использовать.

  • src/pages/register: содержит форму для регистрации нового пользователя.
  • src/pages/signin: содержит форму для авторизации пользователя.
  • src/pages/dashboard: содержит страницу, доступную только для авторизованных пользователей.

Создайте эти страницы в каталоге src/pages. Теперь ваш проект должен включать эти новые файлы:

  • Directorysrc/
    • Directorylib/
      • supabase.ts
    • Directorypages/
      • Directoryapi/
        • Directoryauth/
          • signin.ts
          • signout.ts
          • register.ts
      • register.astro
      • signin.astro
      • dashboard.astro
    • env.d.ts
  • .env
  • astro.config.mjs
  • package.json

register.astro содержит форму для регистрации нового пользователя. Она принимает электронную почту и пароль и отправляет POST-запрос на /api/auth/register.

src/pages/register.astro
---
import Layout from "../layouts/Layout.astro";
---
<Layout title="Register">
<h1>Register</h1>
<p>Already have an account? <a href="/signin">Sign in</a></p>
<form action="/api/auth/register" method="post">
<label for="email" for="email">Email</label>
<input type="email" name="email" id="email" />
<label for="password">Password</label>
<input type="password" name="password" id="password" />
<button type="submit">Login</button>
</form>
</Layout>

В signin.astro содержится форма для авторизации пользователя. Она принимает электронную почту и пароль и отправляет POST запрос на /api/auth/signin. Она также проверяет наличие токенов доступа и обновления. Если они присутствуют, то происходит перенаправление на приборную панель.

src/pages/signin.astro
---
import Layout from "../layouts/Layout.astro";
const { cookies, redirect } = Astro;
const accessToken = cookies.get("sb-access-token");
const refreshToken = cookies.get("sb-refresh-token");
if (accessToken && refreshToken) {
return redirect("/dashboard");
}
---
<Layout title="Sign in">
<h1>Sign in</h1>
<p>New here? <a href="/register">Create an account</a></p>
<form action="/api/auth/signin" method="post">
<label for="email" for="email">Email</label>
<input type="email" name="email" id="email" />
<label for="password">Password</label>
<input type="password" name="password" id="password" />
<button type="submit">Login</button>
</form>
</Layout>

dashboard.astro содержит страницу, доступную только для авторизованных пользователей. Она проверяет наличие токенов доступа и обновления. Если их нет, она перенаправляет на страницу авторизации.

src/pages/dashboard.astro
---
import Layout from "../layouts/Layout.astro";
import { supabase } from "../lib/supabase";
const { cookies, redirect } = Astro;
const accessToken = cookies.get("sb-access-token");
const refreshToken = cookies.get("sb-refresh-token");
if (!accessToken || !refreshToken) {
return redirect("/signin");
}
const { data, error } = await supabase.auth.setSession({
refresh_token: refreshToken.value,
access_token: accessToken.value,
});
if (error) {
cookies.delete("sb-access-token", {
path: "/",
});
cookies.delete("sb-refresh-token", {
path: "/",
});
return redirect("/signin");
}
const email = data?.user?.email;
---
<Layout title="dashboard">
<h1>Welcome {email}</h1>
<p>We are happy to see you here</p>
<form action="/api/auth/signout">
<button type="submit">Sign out</button>
</form>
</Layout>

Чтобы добавить OAuth-аутентификацию в ваш проект, вам нужно отредактировать клиент Supabase, чтобы включить поток аутентификации с помощью pkce. Подробнее о потоках аутентификации вы можете прочитать в документации Supabase.

src/lib/supabase.ts
import { createClient } from "@supabase/supabase-js";
export const supabase = createClient(
import.meta.env.SUPABASE_URL,
import.meta.env.SUPABASE_ANON_KEY,
{
auth: {
flowType: "pkce",
},
},
);

Далее в приборной панели Supabase включите провайдер OAuth, который вы хотите использовать. Список поддерживаемых провайдеров можно найти на вкладке Authentication > Providers вашего проекта Supabase.

В следующем примере в качестве провайдера OAuth используется GitHub. Чтобы подключить свой проект к GitHub, выполните действия, описанные в документации Supabase.

Затем создайте новую конечную точку сервера для обработки обратного вызова OAuth в src/pages/api/auth/callback.ts. Эта конечная точка будет использоваться для обмена кода OAuth на токен доступа и обновления.

src/pages/api/auth/callback.ts
import type { APIRoute } from "astro";
import { supabase } from "../../../lib/supabase";
export const GET: APIRoute = async ({ url, cookies, redirect }) => {
const authCode = url.searchParams.get("code");
if (!authCode) {
return new Response("No code provided", { status: 400 });
}
const { data, error } = await supabase.auth.exchangeCodeForSession(authCode);
if (error) {
return new Response(error.message, { status: 500 });
}
const { access_token, refresh_token } = data.session;
cookies.set("sb-access-token", access_token, {
path: "/",
});
cookies.set("sb-refresh-token", refresh_token, {
path: "/",
});
return redirect("/dashboard");
};

Далее отредактируйте страницу входа и добавьте новую кнопку для входа с провайдером OAuth. Эта кнопка должна отправлять POST-запрос на /api/auth/signin с provider, установленным на имя OAuth-провайдера.

src/pages/signin.astro
---
import Layout from "../layouts/Layout.astro";
const { cookies, redirect } = Astro;
const accessToken = cookies.get("sb-access-token");
const refreshToken = cookies.get("sb-refresh-token");
if (accessToken && refreshToken) {
return redirect("/dashboard");
}
---
<Layout title="Sign in">
<h1>Sign in</h1>
<p>New here? <a href="/register">Create an account</a></p>
<form action="/api/auth/signin" method="post">
<label for="email" for="email">Email</label>
<input type="email" name="email" id="email" />
<label for="password">Password</label>
<input type="password" name="password" id="password" />
<button type="submit">Login</button>
<button value="github" name="provider" type="submit">Sign in with GitHub</button>
</form>
</Layout>

Наконец, отредактируйте конечную точку сервера входа для работы с провайдером OAuth. Если присутствует provider, он будет перенаправлять на провайдера OAuth. В противном случае он будет регистрировать пользователя с помощью электронной почты и пароля.

src/pages/api/auth/signin.ts
import type { APIRoute } from "astro";
import { supabase } from "../../../lib/supabase";
import type { Provider } from "@supabase/supabase-js";
export const POST: APIRoute = async ({ request, cookies, redirect }) => {
const formData = await request.formData();
const email = formData.get("email")?.toString();
const password = formData.get("password")?.toString();
const provider = formData.get("provider")?.toString();
const validProviders = ["google", "github", "discord"];
if (provider && validProviders.includes(provider)) {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: provider as Provider,
options: {
redirectTo: "http://localhost:4321/api/auth/callback"
},
});
if (error) {
return new Response(error.message, { status: 500 });
}
return redirect(data.url);
}
if (!email || !password) {
return new Response("Email and password are required", { status: 400 });
}
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
return new Response(error.message, { status: 500 });
}
const { access_token, refresh_token } = data.session;
cookies.set("sb-access-token", access_token, {
path: "/",
});
cookies.set("sb-refresh-token", refresh_token, {
path: "/",
});
return redirect("/dashboard");
};

После создания конечной точки обратного вызова OAuth и редактирования страницы входа и конечной точки сервера ваш проект должен иметь следующую структуру файлов:

  • Directorysrc/
    • Directorylib/
      • supabase.ts
    • Directorypages/
      • Directoryapi/
        • Directoryauth/
          • signin.ts
          • signout.ts
          • register.ts
          • callback.ts
      • register.astro
      • signin.astro
      • dashboard.astro
    • env.d.ts
  • .env
  • astro.config.mjs
  • package.json

Дополнительные руководства по бэкенд-сервисам