Supabase даёт браузеру готовые Auth и Postgres API, но безопасность появляется только после включения Row Level Security. Соберём регистрацию, вход и личные заметки на обычном JavaScript. Пользователь увидит только свои строки, publishable key останется в клиенте, а service_role key туда никогда не попадёт.
Новичок часто начинает с формы входа и быстро упирается в сервер, базу, хеширование паролей, сессии и права. Supabase берёт инфраструктуру на себя: Auth выдаёт JWT, Postgres хранит данные, а клиентская библиотека прикладывает сессию к запросам. Это не означает, что проверок больше нет.
supabase.auth.signUp создаёт пользователя и при включённом подтверждении отправляет письмо.
signInWithPassword получает сессию, которую клиент хранит и обновляет автоматически.
RLS проверяет auth.uid() на каждой операции, даже если запрос собран вручную в DevTools.
Из каких слоёв состоит Supabase-приложение
Проект содержит статическую страницу, app.js и SQL для таблицы. В Supabase Dashboard мы создаём проект, берём Project URL и publishable key. Форма регистрации вызывает signUp, форма входа вызывает signInWithPassword. После появления сессии приложение запрашивает notes.
| Этап | Что происходит | Признак результата |
|---|---|---|
| Auth | Email и пароль отправляются Supabase Auth | Получена сессия или письмо |
| JWT | Клиент хранит и прикладывает access token | Запрос связан с пользователем |
| RLS | Postgres сравнивает auth.uid() и user_id | Чужая строка недоступна |
| UI | JavaScript рисует только разрешённые заметки | Список обновился без перезагрузки |
У проекта есть один главный маршрут: получить входные данные, проверить их, выполнить действие и показать результат. Если каждый этап можно проверить отдельно, ошибка перестаёт быть загадкой.

От входных данных до видимого результата. JWT связывает запрос с пользователем до проверки строки
.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 решает, какие строки доступны этой личности.

Что отличает устойчивый проект от случайного успеха. Право доступа должно проверяться базой
Почему 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 и выполняет SQL | user_id имеет внешний ключ |
| RLS policy | Разрешает строки конкретному auth.uid() | select и insert проверяются отдельно |
Publishable key идентифицирует проект и рассчитан на открытый клиент. Его безопасность опирается на политики, ограничения Auth и схему данных. service_role предназначен для доверенного сервера и обходит RLS. Если он утёк, пользователь сможет действовать с повышенными правами. Не путайте эти ключи.
.eq("user_id", user.id), пользователь может убрать фильтр. RLS обязана возвращать только разрешённые строки на уровне базы.Проверяем двух пользователей и запрещённый доступ
Создайте два тестовых email. Под первым добавьте заметки «Python» и «SQL», выйдите и войдите вторым. Его список должен быть пуст. Добавьте заметку второго пользователя и вернитесь к первому: он должен видеть только свои две строки. Затем временно попробуйте выполнить insert с user_id первого аккаунта из сессии второго. Политика должна вернуть ошибку.
- Зарегистрируйте пользователя и разберите состояние до подтверждения email.
- Войдите и добавьте две заметки с текущим user.id.
- Создайте второй аккаунт и подтвердите, что список изолирован.
- Попробуйте подменить user_id и получите отказ RLS.
- Добавьте signOut и очистку интерфейса после выхода.
- Перезагрузите страницу и проверьте восстановление сессии.
- Регистрация и вход дают понятный результат.
- Каждый пользователь видит только свои notes.
- Подмена user_id отклоняется базой.
- service_role отсутствует во всех клиентских файлах.
Главный тест проекта проводится двумя пользователями, а не одной красивой формой. Если второй аккаунт не может прочитать и записать чужую строку, ваша авторизация работает там, где должна: рядом с данными.

Четыре проверки перед следующим шагом. Два аккаунта дают самый наглядный тест
Развиваем проект без обхода RLS
После базового примера добавьте update и delete с отдельными политиками using и with check. Затем вынесите повторяющийся рендер списка в функцию и отображайте техническую ошибку дружелюбным текстом. Для реального продукта настройте допустимые redirect URL, политику паролей, ограничение запросов и резервное копирование.
Как изучить тему в Кодике
В Кодике начните с событий формы и async/await в JavaScript. Затем закрепите SELECT и INSERT в SQL без Supabase. После этого соедините слои: форма вызывает функцию, функция отправляет запрос, а политика базы решает, разрешён ли доступ.
| Шаг | Что изучить в Кодике | Мини-проверка |
|---|---|---|
| 1 | Формы и события JavaScript | Получить email, пароль и текст заметки |
| 2 | Promise и async/await | Показать успех и ошибку без перезагрузки |
| 3 | SQL: таблица и внешний ключ | Создать notes с user_id |
| 4 | Auth и JWT | Объяснить разницу сессии и пользователя |
| 5 | RLS policies | Изолировать данные двух аккаунтов |
В тренажёре Кодика сначала напишите функции signIn и addNote с фиктивным клиентом. Затем восстановите SQL-политику словами: «разрешить, если текущий uid равен user_id». Такой перевод между кодом и правилом помогает не копировать безопасность вслепую.
Этот ключ обходит RLS. Отзовите его и оставьте в браузере только publishable key.
После включения все операции будут запрещены. Создайте явные правила для каждой нужной операции.
Берите текущего пользователя из проверенной сессии и всё равно проверяйте значение политикой Postgres.
Логируйте техническую причину безопасно, а пользователю давайте понятное действие без лишнего раскрытия аккаунтов.
Что получится в итоге

Обычная страница регистрирует пользователя, сохраняет заметки в Postgres и подтверждает на двух аккаунтах, что RLS не отдаёт чужие строки.
Можно ли хранить publishable key в браузере?
Да, он создан для публичного клиента. Безопасность данных обеспечивают RLS и другие серверные правила.
Почему нельзя хранить service_role там же?
Этот ключ имеет повышенные права и обходит RLS, поэтому принадлежит только доверенной серверной среде.
Чем аутентификация отличается от авторизации?
Auth подтверждает личность, а RLS решает, какие строки этой личности разрешено читать или менять.
Как понять, что проект действительно работает?
Проверьте основной сценарий, неверный ввод, повторный запуск и один граничный случай. Затем объясните путь данных своими словами.
Можно ли начать с готового кода из статьи?
Да. Добейтесь результата, измените одно правило и соберите ключевой файл заново без копирования.
События и async/await закрепите в курсе JavaScript, а основы запросов к базе в курсе SQL. Повторите HTTP по статье про Fetch API.
Затем сравните хранение локального списка в Todo List на JavaScript с серверной таблицей и разберите сортировку SQL.