Как сделать регистрацию и личный кабинет через Supabase Auth
Пошаговый гайд по регистрации, подтверждению email, сессиям, профилю и защите данных в личном кабинете через Supabase Auth.
Человек 2.0Что забрать из статьи
- 1Регистрация считается готовой после подтверждения email, входа, восстановления сессии и повторного чтения профиля.
- 2Publishable key допустим во frontend; secret key остаётся только на доверенном backend и обходит RLS.
- 3Grants, RLS и тест двумя пользователями должны блокировать чтение, изменение и удаление чужого профиля.
Что должно работать в готовом сценарии
Форма регистрации сама по себе ещё не создаёт личный кабинет. Рабочий сценарий начинается с новой учётной записи и заканчивается только тогда, когда человек подтвердил email, вошёл, увидел свои данные, обновил профиль, перезагрузил страницу и не получил доступ к чужой записи.
В этом гайде мы соберём именно такой путь: Supabase Auth отвечает за личность и сессию, таблица public.profiles хранит данные кабинета, а права Postgres и Row Level Security ограничивают операции владельцем строки. Пример ориентирован на JavaScript и supabase-js v2; для Next.js с серверным рендерингом отдельно разберём cookie-сессию.
Что понадобится до начала
Нужны проект Supabase, приложение на JavaScript или TypeScript и пакет @supabase/supabase-js. В клиентском приложении используйте Project URL и актуальный publishable key. Такой ключ можно включать в браузерный код, но он не защищает данные сам по себе: до входа запросы идут с ролью anon, после входа права определяют пользовательский JWT, grants и RLS.
Secret key предназначен только для доверенного backend и обходит RLS. Никогда не помещайте его во frontend, мобильное приложение, публичный репозиторий, URL или клиентские логи. Старые anon и service_role ещё встречаются в проектах, но Supabase относит их к legacy JWT-ключам и рекомендует переходить на publishable и secret keys.
Шаг 1. Создайте безопасный клиент Supabase
Установите клиентскую библиотеку командой npm install @supabase/supabase-js. Затем создайте один браузерный клиент с Project URL и publishable key, переданными через публичные переменные сборки. В статье намеренно нет реальных значений, доменов проекта и ключей.
Проверка шага: приложение собирается, клиент создаётся без ошибки, а в исходниках и логах отсутствует secret key. Не пытайтесь скрыть publishable key как пароль; безопасность пользовательских строк обеспечивается правами базы и RLS.
Шаг 2. Зарегистрируйте пользователя и обработайте подтверждение email
Для регистрации вызовите supabase.auth.signUp({ email, password }). Всегда обрабатывайте оба результата: data и error. Не журналируйте пароль, access token, refresh token или полную сессию.
В hosted-проектах подтверждение email обычно включено. В этом случае signUp возвращает объект пользователя, а session остаётся null. Интерфейс должен показать состояние «Проверьте почту», а не сразу отправлять человека в кабинет. При отключённом подтверждении пользователь и сессия обычно доступны сразу.
Если передаёте emailRedirectTo, адрес должен входить в Redirect URLs allowlist. Site URL используется как маршрут по умолчанию для подтверждения и восстановления. Для production лучше перечислить точные URL, а широкие wildcard оставить локальной разработке и preview-средам.

Шаг 3. Сделайте вход и выход
Вход по email и паролю выполняется через supabase.auth.signInWithPassword({ email, password }). Успех означает, что клиент получил сессию и может делать запросы от роли authenticated. Ошибку показывайте нейтрально: не раскрывайте, существует ли конкретный email, и не пересылайте человеку технический ответ целиком.
Обычный supabase.auth.signOut() использует глобальный scope и завершает активные сессии пользователя. Для выхода только из текущего браузера передайте { scope: "local" }. Refresh tokens отзываются, однако уже выданный access token может действовать до времени exp. Для критичных действий учитывайте это окно отдельно.
Шаг 4. Разделите учётную запись и данные профиля
Схема auth хранит служебные данные Supabase Auth и не открыта через автоматически созданный Data API. Для имени, аватара, настроек и других полей кабинета создайте public.profiles. Идентификатор профиля должен ссылаться на первичный ключ auth.users.id; обычно применяют on delete cascade.
Роль и полномочия нельзя доверять изменяемому user_metadata. Эти данные человек может обновлять. Используйте их для презентационных полей, а права храните в контролируемом приложением источнике.
Запись профиля можно создавать после регистрации из приложения или триггером. Ошибка триггера способна заблокировать signup, поэтому такой путь требует отдельного теста и функции security definer с явно заданным search_path.
Шаг 5. Ограничьте доступ grants и RLS
Включите Row Level Security для public.profiles, выдайте ролям только необходимые права и создайте отдельные политики для операций. Для SELECT и DELETE применяется using. Для INSERT нужен with check. Для UPDATE нужны using и with check, а также подходящая SELECT policy.
Правило владельца сопоставляет текущего authenticated-пользователя с user_id или id строки, например (select auth.uid()) = user_id. Добавьте индекс на поле владельца. Фильтр в интерфейсе не является защитой: запрос можно отправить напрямую.
Проверка должна включать анонимного посетителя, владельца A и постороннего пользователя B. Убедитесь, что B не читает, не создаёт строку с чужим user_id, не меняет владельца и не удаляет запись A. Запрещённая операция иногда возвращает ноль строк без исключения, поэтому проверяйте и состояние базы.

Шаг 6. Загрузите профиль в личном кабинете
После входа получите подтверждённого пользователя через supabase.auth.getUser(), затем запросите профиль по его id. На клиенте состояние кабинета должно различать загрузку, успешный ответ, отсутствие профиля, истёкшую сессию и сетевую ошибку.
При обновлении профиля отправляйте только разрешённые поля. user_id должен определяться из подтверждённой сессии и проходить with check в RLS. После сохранения перечитайте запись из базы и покажите фактически сохранённые данные.
Шаг 7. Правильно восстановите сессию
В чистом браузерном приложении supabase-js сохраняет и обновляет сессию. После перезагрузки дождитесь инициализации Auth, затем загрузите пользователя и профиль. Не показывайте защищённое содержимое до завершения проверки.
В Next.js с SSR используйте @supabase/ssr, отдельный server client на запрос и cookies. Актуальная документация Supabase для Next.js использует Proxy для обновления токенов и записи новых cookies. На сервере не считайте результат getSession доказательством личности: для защищённых страниц и данных используйте проверку, которая обращается к Auth-серверу, например getUser, а в текущем SSR-руководстве — getClaims.
Шаг 8. Добавьте восстановление пароля
Восстановление состоит из двух этапов. Сначала вызовите resetPasswordForEmail с разрешённым redirectTo. После перехода по письму на защищённой странице задайте новый пароль через updateUser({ password }).
Для SSR и PKCE может понадобиться серверный endpoint, который обменивает token_hash через verifyOtp. Маршрут возврата должен быть в allowlist. Не передавайте токен письма в аналитические события, сторонние логи или публичные ссылки.
Как проверить результат двумя пользователями
Создайте два тестовых аккаунта в изолированной среде. Пользователь A регистрируется, подтверждает email, входит, создаёт профиль, меняет имя и после перезагрузки видит сохранённый результат. Пользователь B проходит тот же путь со своей записью.
Теперь отправьте запросы B к записи A: чтение, изменение, попытку подменить владельца и удаление. Все они должны не изменить чужую строку. Затем выйдите из аккаунта и повторите чтение как anon. Зафиксируйте ожидаемый статус интерфейса и фактическое состояние базы.
Критерий готовности: новый человек проходит регистрацию без помощи разработчика, понимает письмо подтверждения, возвращается в приложение, видит свой кабинет, сохраняет данные, повторно входит после закрытия браузера и не может получить чужой профиль.
Типичные ошибки и точная диагностика
После signUp нет перехода в кабинет. Проверьте, включено ли подтверждение email. При session = null это ожидаемое состояние; покажите экран с инструкцией и возможностью повторить письмо.
Ссылка из письма ведёт не туда. Сверьте Site URL, redirectTo и Redirect URLs allowlist. Проверьте точное различие production, preview и локального адреса.
После обновления страницы человек разлогинен. Для браузерного приложения проверьте создание клиента и инициализацию Auth. Для SSR проверьте request-specific client, чтение и запись cookies, Proxy и отсутствие кеширования ответа между пользователями.
Профиль не создаётся. Проверьте внешний ключ, grants, INSERT policy и with check. Если используется триггер, посмотрите его ошибку: сбой триггера может остановить саму регистрацию.
Пользователь видит чужие строки. Остановите выпуск. Проверьте, включён ли RLS, какие grants выданы anon и authenticated, все CRUD-политики и прямой запрос под вторым аккаунтом.
В браузер попал secret key. Уберите источник утечки, создайте новый secret key, замените его во всех доверенных компонентах, проверьте переход и удалите старый. Не публикуйте значение в баг-трекере или переписке.
Ограничения, которые важно учитывать
Supabase Auth берёт на себя хранение учётных записей, выпуск сессий и основные auth-маршруты. Он не проектирует за вас модель профиля, роли, бизнес-права, тексты ошибок, резервное восстановление и пользовательский опыт.
Защита от перебора, CAPTCHA, rate limits, MFA и дополнительные проверки зависят от риска продукта. Для платежей, медицинских данных и других чувствительных сценариев нужен отдельный разбор угроз и тестирование безопасности.
Повторный signUp на существующий email может давать нейтральный ответ в зависимости от настроек подтверждения и защиты от перечисления пользователей. Не стройте интерфейс на обещании точной ошибки «email уже зарегистрирован».
Частые вопросы
Можно ли хранить имя прямо в user_metadata? Да, для презентационных полей. Для ролей и полномочий этот источник не подходит, потому что пользователь может менять свои метаданные.
Можно ли показывать publishable key во frontend? Да. Это публичный идентификатор клиента. Данные защищают точные grants и RLS. Secret key во frontend запрещён.
Почему после signOut запрос ещё может пройти? Уже выпущенный JWT остаётся действительным до exp. Sign out отзывает refresh tokens и удаляет локальную сессию, но не отзывает прошлое время у access token.
Что делать дальше
Начните с одного законченного пути: signup → подтверждение email → вход → профиль → обновление → повторный вход → проверка вторым пользователем. Только после этого добавляйте OAuth, MFA, роли и соседние разделы кабинета.
Если интерфейс уже готов, сначала проверьте весь путь от экрана до сохранённых данных в статье «Есть красивый frontend — что делать дальше, чтобы получился работающий продукт».
На воркшопе «Человек 2.0» Codex + Hermes участники собирают базу, пользователей, личный кабинет, заявки, профили, заказы, админку и CRM на своём проекте. Посмотреть программу можно на официальной странице воркшопа.
Источники
- Supabase Auth: https://supabase.com/docs/guides/auth
- Password-based Auth: https://supabase.com/docs/guides/auth/passwords
- Server-Side Auth for Next.js: https://supabase.com/docs/guides/auth/server-side/nextjs
- User Management: https://supabase.com/docs/guides/auth/managing-user-data
- User sessions: https://supabase.com/docs/guides/auth/sessions
- Signing out: https://supabase.com/docs/guides/auth/signout
- Redirect URLs: https://supabase.com/docs/guides/auth/redirect-urls
- API keys: https://supabase.com/docs/guides/getting-started/api-keys
- Row Level Security: https://supabase.com/docs/guides/database/postgres/row-level-security
- Человек 2.0 — программа воркшопа: https://human20.app/workshop
- Материал и ссылки проверены 10 сентября 2026 года. Технический TTL — 30 дней.