ФронтендБэкендJavaScriptSQL

Supabase + JavaScript: регистрация, вход и база без своего бэкенда

Подключаем Supabase к обычной HTML-странице, создаём регистрацию и вход, сохраняем заметки в Postgres и закрываем чужие строки через Row Level Security.

Кодик

Автор

7 мин чтения

Supabase даёт браузеру готовые Auth и Postgres API, но безопасность появляется только после включения Row Level Security. Соберём регистрацию, вход и личные заметки на обычном JavaScript. Пользователь увидит только свои строки, publishable key останется в клиенте, а service_role key туда никогда не попадёт.

Новичок часто начинает с формы входа и быстро упирается в сервер, базу, хеширование паролей, сессии и права. Supabase берёт инфраструктуру на себя: Auth выдаёт JWT, Postgres хранит данные, а клиентская библиотека прикладывает сессию к запросам. Это не означает, что проверок больше нет.

1Регистрируем

supabase.auth.signUp создаёт пользователя и при включённом подтверждении отправляет письмо.

2Входим

signInWithPassword получает сессию, которую клиент хранит и обновляет автоматически.

3Защищаем

RLS проверяет auth.uid() на каждой операции, даже если запрос собран вручную в DevTools.

Из каких слоёв состоит Supabase-приложение

Проект содержит статическую страницу, app.js и SQL для таблицы. В Supabase Dashboard мы создаём проект, берём Project URL и publishable key. Форма регистрации вызывает signUp, форма входа вызывает signInWithPassword. После появления сессии приложение запрашивает notes.

ЭтапЧто происходитПризнак результата
AuthEmail и пароль отправляются Supabase AuthПолучена сессия или письмо
JWTКлиент хранит и прикладывает access tokenЗапрос связан с пользователем
RLSPostgres сравнивает auth.uid() и user_idЧужая строка недоступна
UIJavaScript рисует только разрешённые заметкиСписок обновился без перезагрузки

У проекта есть один главный маршрут: получить входные данные, проверить их, выполнить действие и показать результат. Если каждый этап можно проверить отдельно, ошибка перестаёт быть загадкой.

Путь запроса Supabase
От входных данных до видимого результата. JWT связывает запрос с пользователем до проверки строки

Сначала включите RLS, потом подключайте таблицу к браузеру. Скрытая кнопка и фильтр .eq в JavaScript не являются защитой. Пользователь может отправить запрос сам. Политика должна находиться рядом с данными и срабатывать для любого клиента.

Создаём Auth, таблицу и JavaScript-клиент

В SQL Editor выполните schema.sql из исходников статьи. Затем вставьте Project URL и publishable key в app.js. Этот публичный ключ предназначен для клиентского приложения и ограничивается RLS. Ключ service_role обходит политики, поэтому его нельзя помещать в HTML, JavaScript, мобильное приложение или публичный репозиторий. Запускайте страницу через локальный сервер, например npx serve ., а не открывайте двойным кликом.

import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

const supabase = createClient(
  "https://YOUR_PROJECT.supabase.co",
  "YOUR_PUBLISHABLE_KEY"
);

export async function signUp(email, password) {
  const { data, error } = await supabase.auth.signUp({ email, password });
  if (error) throw error;
  return data.user;
}

export async function signIn(email, password) {
  const { error } = await supabase.auth.signInWithPassword({ email, password });
  if (error) throw error;
  return loadNotes();
}

export async function addNote(text) {
  const { data: { user } } = await supabase.auth.getUser();
  if (!user) throw new Error("Сначала войдите");

  const { error } = await supabase
    .from("notes")
    .insert({ text, user_id: user.id });
  if (error) throw error;
  return loadNotes();
}

export async function loadNotes() {
  const { data, error } = await supabase
    .from("notes")
    .select("id, text, created_at")
    .order("created_at", { ascending: false });
  if (error) throw error;
  return data;
}
Что должно произойти после запуска
  • Регистрация создаёт пользователя, а при подтверждении email сообщает о необходимости открыть письмо.
  • После входа getUser возвращает проверенного пользователя текущей сессии.
  • Добавленная заметка появляется в списке без отдельного собственного сервера.
  • Второй аккаунт получает пустой список и не видит строк первого пользователя.

JavaScript не отправляет пароль в вашу таблицу и не хранит его самостоятельно. Auth отвечает за личность, JWT связывает запрос с пользователем, а RLS решает, какие строки доступны этой личности.

Фильтр интерфейса и настоящая RLS
Что отличает устойчивый проект от случайного успеха. Право доступа должно проверяться базой

Почему publishable key можно показать, а service_role нельзя

При signInWithPassword Supabase Auth возвращает сессию. Библиотека supabase-js по умолчанию сохраняет её в localStorage браузера и обновляет access token. Запрос .from("notes") идёт к автоматически созданному Data API с Authorization. Внутри Postgres функция auth.uid() извлекает id из проверенного JWT.

ЧастьОтветственностьЧто проверить
supabase-jsФормирует Auth и Data API запросыКлиент создан с publishable key
AuthПроверяет email, пароль и сессиюПароль не попадает в notes
JWTСвязывает запрос с user idТокен обновляется библиотекой
PostgresХранит notes и выполняет SQLuser_id имеет внешний ключ
RLS policyРазрешает строки конкретному auth.uid()select и insert проверяются отдельно

Publishable key идентифицирует проект и рассчитан на открытый клиент. Его безопасность опирается на политики, ограничения Auth и схему данных. service_role предназначен для доверенного сервера и обходит RLS. Если он утёк, пользователь сможет действовать с повышенными правами. Не путайте эти ключи.

Фильтр в запросе не заменяет политику. Даже если loadNotes добавит .eq("user_id", user.id), пользователь может убрать фильтр. RLS обязана возвращать только разрешённые строки на уровне базы.

Проверяем двух пользователей и запрещённый доступ

Создайте два тестовых email. Под первым добавьте заметки «Python» и «SQL», выйдите и войдите вторым. Его список должен быть пуст. Добавьте заметку второго пользователя и вернитесь к первому: он должен видеть только свои две строки. Затем временно попробуйте выполнить insert с user_id первого аккаунта из сессии второго. Политика должна вернуть ошибку.

Проверка своими руками
  1. Зарегистрируйте пользователя и разберите состояние до подтверждения email.
  2. Войдите и добавьте две заметки с текущим user.id.
  3. Создайте второй аккаунт и подтвердите, что список изолирован.
  4. Попробуйте подменить user_id и получите отказ RLS.
  5. Добавьте signOut и очистку интерфейса после выхода.
  6. Перезагрузите страницу и проверьте восстановление сессии.
Готово, если выполняются все пункты
  • Регистрация и вход дают понятный результат.
  • Каждый пользователь видит только свои notes.
  • Подмена user_id отклоняется базой.
  • service_role отсутствует во всех клиентских файлах.

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

Четыре проверки Auth и базы
Четыре проверки перед следующим шагом. Два аккаунта дают самый наглядный тест

Развиваем проект без обхода RLS

После базового примера добавьте update и delete с отдельными политиками using и with check. Затем вынесите повторяющийся рендер списка в функцию и отображайте техническую ошибку дружелюбным текстом. Для реального продукта настройте допустимые redirect URL, политику паролей, ограничение запросов и резервное копирование.

Как изучить тему в Кодике

В Кодике начните с событий формы и async/await в JavaScript. Затем закрепите SELECT и INSERT в SQL без Supabase. После этого соедините слои: форма вызывает функцию, функция отправляет запрос, а политика базы решает, разрешён ли доступ.

ШагЧто изучить в КодикеМини-проверка
1Формы и события JavaScriptПолучить email, пароль и текст заметки
2Promise и async/awaitПоказать успех и ошибку без перезагрузки
3SQL: таблица и внешний ключСоздать notes с user_id
4Auth и JWTОбъяснить разницу сессии и пользователя
5RLS policiesИзолировать данные двух аккаунтов

В тренажёре Кодика сначала напишите функции signIn и addNote с фиктивным клиентом. Затем восстановите SQL-политику словами: «разрешить, если текущий uid равен user_id». Такой перевод между кодом и правилом помогает не копировать безопасность вслепую.

service_role добавлен в app.js

Этот ключ обходит RLS. Отзовите его и оставьте в браузере только publishable key.

RLS включён без policies

После включения все операции будут запрещены. Создайте явные правила для каждой нужной операции.

Доверие к user_id из формы

Берите текущего пользователя из проверенной сессии и всё равно проверяйте значение политикой Postgres.

Ошибки Auth показываются как есть

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

Сверьтесь с первичным источником. Команды, версии и ограничения примера проверяйте по официальной документации Supabase Auth. Если интерфейс изменился, первичная документация важнее старого скриншота.

Что получится в итоге

Готовый результат: Supabase + JavaScript: регистрация, вход и база без своего бэкенда
Обычная страница регистрирует пользователя, сохраняет заметки в Postgres и подтверждает на двух аккаунтах, что RLS не отдаёт чужие строки.

Короткие ответы
Можно ли хранить publishable key в браузере?

Да, он создан для публичного клиента. Безопасность данных обеспечивают RLS и другие серверные правила.

Почему нельзя хранить service_role там же?

Этот ключ имеет повышенные права и обходит RLS, поэтому принадлежит только доверенной серверной среде.

Чем аутентификация отличается от авторизации?

Auth подтверждает личность, а RLS решает, какие строки этой личности разрешено читать или менять.

Как понять, что проект действительно работает?

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

Можно ли начать с готового кода из статьи?

Да. Добейтесь результата, измените одно правило и соберите ключевой файл заново без копирования.

Соедините интерфейс, запросы и SQL в один понятный маршрут

События и async/await закрепите в курсе JavaScript, а основы запросов к базе в курсе SQL. Повторите HTTP по статье про Fetch API.

Затем сравните хранение локального списка в Todo List на JavaScript с серверной таблицей и разберите сортировку SQL.