Перейти до вмісту

Основи безпеки

Чому Supabase перенаправляє посилання з листів на localhost:3000

Якщо Supabase перенаправляє на localhost:3000, Site URL так і не змінили або вашої адреси немає в Redirect URLs. Як виправити і якої маски уникати.

Vlad Tkachenko9 хв читання
Дороговказ, чия освітлена стрілка показує геть від будинку, до якого має вести, у бік ноутбука з темним екраном.

Коротко

  • Перенаправлення Supabase на localhost:3000 іде від Site URL на сторінці URL Configuration. Проєкт починає з http://localhost:3000, і кожне посилання з листа повертається до неї, коли адреси, яку попросив ваш застосунок, немає у списку Redirect URLs.
  • Той, хто натиснув посилання з реєстрації, зазвичай уже підтверджений. Supabase підтверджує обліковий запис, перш ніж відправити людину далі, тож вона може увійти на вашому справжньому сайті.
  • Поставте в Site URL свою робочу адресу й внесіть у список точні адреси, якими користуєтеся. Маска, настільки широка, що підходить до чужого застосунку, дозволяє підробленому посиланню відправити туди ваших користувачів уже з виконаним входом.

Новий користувач реєструється у вашому робочому застосунку, відкриває лист із підтвердженням і натискає кнопку. Його браузер відкриває сторінку з написами "This site can't be reached" і "localhost refused to connect" (так в англомовному Chrome), над адресою, яка починається так:

http://localhost:3000/#access_token=…&expires_at=…&expires_in=…&refresh_token=…&sb=&token_type=bearer&type=signup

Якщо ваш застосунок використовує потік PKCE, вона натомість закінчується на ?code=…. Ви збудували застосунок у Lovable, Bolt, v0, Cursor, Claude Code чи Replit, у попередньому перегляді він працював бездоганно, а вхід із паролем досі працює.

Перенаправлення Supabase на localhost означає, що Supabase Auth, частина Supabase, яка впускає людей, виконала свою роботу, а потім відправила людину на Site URL вашого проєкту, де досі стоїть http://localhost:3000. Ця адреса означає "цей компʼютер". На компʼютері вашого користувача там нічого немає. Виправлення складається з двох налаштувань на одній сторінці панелі Supabase.

Ось у чому помиляються швидкі поради в мережі: маска, яка прибирає це повідомлення одним рядком, водночас дозволяє посиланню незнайомця відправити ваших користувачів на його сторінку вже з виконаним входом. Налаштування нижче змушують посилання працювати й тримають ці двері зачиненими.

Що означає перенаправлення Supabase на localhost?

Воно означає, що Supabase відправив людину на типову адресу вашого проєкту, бо не прийняв кращої.

Кожен лист, який Supabase надсилає за вас, чи то підтвердження реєстрації, magic link, скидання пароля чи запрошення, спершу веде на власну адресу вашого проєкту. Посібник із шаблонів листів Supabase показує форму: https://project-ref.supabase.co/auth/v1/verify?token=…&type=email&redirect_to=https://example.com/path. Там Supabase перевіряє токен, а потім відправляє браузер туди, куди вказує redirect_to.

Уявіть дороговказ біля дверей Supabase. Кожна людина, яка відкриває посилання, входить біля дверей, а потім іде далі з ключем у руці вздовж однієї зі стрілок. Стрілки ставите ви. Site URL і є тією стрілкою, якою йдуть усі, коли жодна інша не підходить, і в новому проєкті вона показує на http://localhost:3000: майстерню, де збудували застосунок, а на будь-чиїй іншій машині порожню кімнату.

Посібник із перенаправлень Supabase каже, що Site URL задає типове перенаправлення, коли в коді не вказано redirectTo, просить змінити її з http://localhost:3000 на вашу робочу адресу й називає це налаштування критично важливим для підтверджень поштою та скидання паролів.

Чому Supabase ігнорує адресу, яку попросив мій застосунок?

Тому що цієї адреси немає у вашому списку Redirect URLs, і Supabase бере іншу, нічого вам не кажучи.

Ваш застосунок може вказати, куди людям потрапити. У JavaScript-бібліотеці Supabase це emailRedirectTo у signUp і signInWithOtp та redirectTo у resetPasswordForEmail і signInWithOAuth. Далі Supabase Auth вирішує в короткій функції, у такому порядку:

  1. Адреса, яку попросив ваш застосунок, якщо вона збігається із записом у вашому списку Redirect URLs або лежить на тому самому домені, що й ваша Site URL.
  2. Адреса сторінки, з якої прийшов запит, як її передав браузер, за тим самим правилом.
  3. Інакше Site URL. Supabase не видає жодної помилки, а консоль вашого застосунку нічого не показує.

Це рішення ухвалюється, коли лист надсилають, і обрана адреса потрапляє в посилання. Коли хтось відкриває посилання, Supabase ще раз перевіряє цю адресу за вашими налаштуваннями такими, якими вони є в цю мить. Власна відповідь Supabase з усунення несправностей зводить правило до одного речення: щоб опція redirectTo спрацювала, треба внести точну URL-адресу в налаштування Redirect URLs.

Supabase пробує адресу, яку попросив ваш застосунок, потім сторінку, з якої прийшов запит. Коли жодної з них немає у списку, вирішує Site URL.

Те саме налаштування вирішує, куди "Sign in with Google" повертає людей, тож кнопка Google, яка приводить на localhost, має ту саму причину. Якщо ж кнопка Google зупиняється раніше, на сторінці з "Unsupported provider: provider is not enabled", справа в іншому: у перемикачі в Supabase і списку в Google.

Чому ви не побачили цього до запуску

Тому що обидва способи, якими ви перевіряли, ніколи не доходять до Site URL.

  • Вхід із паролем відбувається всередині сторінки. Немає ні посилання з листа, ні проходу через двері Supabase, тож дороговказ ніхто не читає.
  • Якщо ви будуєте в Cursor чи Claude Code і ваш сервер розробки працював на порту 3000, коли ви перевіряли лист, посилання відкрило вашу власну локальну копію й виглядало так, ніби все працює.
  • На вбудованому бекенді Lovable ви, можливо, ніколи з цим не зіткнетеся.Документація Lovable про автентифікацію каже, що під час публікації він ставить у Site URL вашу опубліковану адресу на lovable.app, а власний домен додає до redirect URLs, коли ви його підключаєте. Та сама сторінка каже, що застосунки з вашим власним проєктом Supabase не отримують автоматичних оновлень.

Чи підтверджений обліковий запис, хоча сторінка не відкрилася?

Так, якщо це підтвердження реєстрації. Supabase спершу підтверджує і лише потім відправляє браузер далі.

У коді, який обробляє посилання, обліковий запис підтверджується до того, як іде перенаправлення. Сторінка, що не відкривається, зʼявляється, коли роботу вже зроблено. Тож скажіть людині, що вона може увійти на вашому робочому сайті з паролем, який обрала. Повторне натискання того самого листа не допоможе: посилання спрацьовує один раз, а на друге натискання приходить "Email link is invalid or has expired".

Magic link чи скидання пароля працюють інакше. Цей вхід пішов на localhost і там лишився, тож коли ви виправите налаштування, людині потрібен новий лист.

Як виправити перенаправлення Supabase на localhost

Змініть Site URL, а потім внесіть у список інші адреси, куди ви відправляєте людей. Обидва налаштування є на сторінці URL Configuration у розділі Authentication панелі Supabase.

Поставте в Site URL свою робочу адресу з https:// попереду.https://yourapp.com, повністю. Без https:// Supabase читає її як шлях на власній адресі, і людина натомість потрапляє у ваш проєкт Supabase з "No API key found in request". Після цього будь-яку адресу на цьому домені приймають з будь-яким шляхом без запису в списку, тож https://yourapp.com/welcome працює, щойно Site URL правильна.

Додайте інші адреси в Redirect URLs. Версію з www, якщо люди нею користуються, опубліковану адресу в білдері (https://yourapp.lovable.app, наприклад), якщо люди там реєструються, і більше нічого. Запис є шаблоном, тож https://yourapp.com/** охоплює кожну сторінку цього одного домену.

Перевірте, що просить ваш код. Якщо він передає window.location.origin, адреса буде тією, на якій відкрили сторінку, і кожна адреса, яку це може дати, має лежати на домені вашої Site URL або бути в списку. Порівняйте їх літера в літеру: з www чи без, з https чи без. Запис без /** у кінці збігається з цією точною адресою і ні з чим під нею.

Перевірте шаблони листів. Шаблон, який сам будує посилання з {{ .SiteURL }}, відправляє всіх на Site URL, хоч би що попросив ваш застосунок. Посібник Supabase з перенаправлень каже, що з опцією redirectTo вам, можливо, доведеться замінити {{ .SiteURL }} на {{ .RedirectTo }} у шаблонах листів.

Що ви бачитеЩо змінити
Кожне посилання відкриває localhost:3000Site URL
Посилання відкривають вашу Site URL замість адреси, яку попросив ваш кодДодайте цю адресу в Redirect URLs, літера в літеру
Робочий домен працює, а опублікована адреса в білдері ніДодайте опубліковану адресу в Redirect URLs
Посилання відкриває вашу адресу Supabase з "No API key found in request"Додайте https:// до Site URL
Адреса, яку просить ваш застосунок, є в списку, а посилання все одно її ігнорує{{ .SiteURL }} у шаблоні листа

Потім зареєструйтеся ще раз з адресою, якою ще не користувалися. Лист, надісланий до виправлення зі стандартним посиланням Supabase і відкритий у межах своєї години, тепер приводить на ваш робочий сайт, бо Supabase ще раз перевіряє адресу, коли посилання відкривають. Це так, якщо http://localhost:3000 більше немає у вашому списку.

Які Redirect URLs безпечно дозволяти?

Точні адреси, які належать вам. Маска доречна для деплоїв попереднього перегляду, і лише обмежена вашою командою чи проєктом.

Посібник Supabase з перенаправлень каже це прямо: "globstar" (**) корисний для локальної розробки й адрес попереднього перегляду, але в продакшні він радить задавати точний шлях перенаправлення для вашої Site URL.

Причина в тому, що мандрує разом із людиною. У типовому потоці Supabase записує нову сесію в адресу, куди її відправляє: код, який її будує, дописує #access_token=…&refresh_token=… до адреси перенаправлення. Будь-яка сторінка за цією адресою може прочитати ці два значення, і з ними вона стає цим користувачем. На дороговказі саме стрілка вирішує, хто отримає ключ.

Тож запис, що підходить до адреси, яку контролює хтось інший, стає входом в облікові записи ваших користувачів. Будь-хто може попросити ваш проєкт надіслати одному з ваших користувачів посилання для входу, скориставшись публічним ключем, який стоїть у вашому застосунку навмисно, і вказати свою сторінку як місце призначення. Лист, що приходить, справді від вашого застосунку. Потік PKCE цього не змінює, бо людина, яка написала це посилання, сама почала вхід, а PKCE віддає сесію тому, хто його почав.

Шаблони, що підходять до адрес, які можуть створити інші:

  • ** сам по собі підходить до будь-якої адреси, яка існує.
  • https://*.vercel.app/** підходить до кожного деплою Vercel в інтернеті, а створити такий може будь-хто. Власний приклад Supabase містить вашу команду: https://*-<team-or-account-slug>.vercel.app/**.
  • https://*.lovable.app/**, https://*.netlify.app/** і та сама форма на домені будь-якого білдера роблять те саме для цього білдера. Приклад Supabase для Netlify теж обмежений: https://**--my_org.netlify.app/**.
Точний запис відсилає підроблене посилання додому. Маска, що підходить до чужих застосунків, дозволяє йому доставити їм вхід.

Якщо ви щойно розвʼязали це, вставивши маску, перегляньте список ще сьогодні.

Запуск також стає моментом, коли переглядають інші налаштування, що важать лише в продакшні. Наше безкоштовне сканування читає ваш робочий застосунок ззовні й показує, до чого може дістатися незнайомець: таблиці, які відповідають будь-кому, секретні ключі в коді, який надсилає ваш застосунок, відсутні заголовки, сертифікат. Воно триває близько 20 секунд і не потребує облікового запису: проскануйте свій застосунок.

Стежити за робочим застосунком після запуску

Це налаштування виправляють один раз. Те, що ваш застосунок надсилає відвідувачам, змінюється з кожним деплоєм, і за цією частиною можна стежити ззовні.

Reeve Monitor і далі перевіряє ваш робочий застосунок:

  • чи він доступний, кожні 60 секунд, щоб "посилання не працює" можна було відрізнити від "сайт лежить"
  • усі девʼять перевірок щогодини, до трьох застосунків, разом із секретними ключами в коді, який ваш застосунок надсилає відвідувачам
  • повідомлення, коли результат змінюється, щоб проблема, яка пішла з пізнішим деплоєм, дійшла до вас без того, щоб ви самі йшли дивитися
  • щомісячний звіт простою мовою про те, що він бачив

Reeve Care тримає копію вашої бази даних Supabase разом з обліковими записами:

  • зашифрована копія за розкладом, у власному сховищі Reeve в ЄС, поза вашим обліковим записом Supabase
  • схема auth, де живуть облікові записи ваших користувачів, щоб вони повернулися разом з усім іншим
  • кожна копія перевірена, перш ніж зарахуватися, підрахунком рядків у кожній таблиці
  • ваші вивантажені файли теж, щойно ви підключите доступ до Storage
  • відновлення в один клік, із безпечною копією поточного стану, зробленою заздалегідь
  • усе, що робить Monitor

Що зробити зараз

Що робити

  • Поставте в Site URL на сторінці URL Configuration свою робочу адресу разом з https://.
  • Додайте в Redirect URLs інші точні адреси, куди ви відправляєте людей, наприклад версію з www і опубліковану адресу в білдері.
  • Переконайтеся, що кожна адреса, яку ваш код передає як emailRedirectTo чи redirectTo, є в списку, і використовуйте {{ .RedirectTo }} у шаблонах, які мають за нею йти.
  • Скажіть усім, хто після реєстрації побачив сторінку localhost, що їхній обліковий запис підтверджений. Magic links і скидання пароля потребують нового листа.
  • Залиште маски лише для попереднього перегляду вашої команди й ніколи не дозволяйте всі застосунки на домені білдера.

Якщо люди натискають нове посилання і чують, що воно прострочене, для цього повідомлення є окрема сторінка. 10-хвилинний чекліст безпеки охоплює те, що зазвичай лишається відкритим у щойно запущеному застосунку.

Поширені запитання

Чому лист підтвердження від Supabase відкриває localhost:3000?

Тому що Supabase відправив людину на Site URL вашого проєкту, а Site URL досі http://localhost:3000. Supabase бере її щоразу, коли адреси, яку попросив ваш застосунок, немає у списку Redirect URLs або коли застосунок не попросив жодної. Змініть її на сторінці URL Configuration у розділі Authentication.

Чи підтверджений обліковий запис, якщо посилання відкрило localhost?

Так, якщо це підтвердження реєстрації. Supabase спершу підтверджує адресу і лише потім відправляє браузер далі, тож сторінка, що не відкрилася, зʼявляється, коли роботу вже зроблено. Людина може увійти на вашому робочому сайті з паролем, який обрала. Magic link чи скидання пароля треба надіслати заново.

Я додав свій сайт у Redirect URLs, а посилання досі веде на localhost. Чому?

Запис має збігатися з адресою, яку пропонують Supabase, літера в літеру: з www чи без, з https чи без, і шлях, бо запис без /** у кінці збігається лише з цією однією адресою. Якщо вони збігаються, а посилання все одно її ігнорує, ваш шаблон листа будує посилання з {{ .SiteURL }}. І виправте саму Site URL, бо до неї повертається все інше.

Чи треба додавати Site URL у список Redirect URLs?

Ні. Supabase приймає будь-яку адресу на домені самої Site URL без запису в списку, з будь-яким шляхом. Список потрібен для інших адрес: версії з www, опублікованої адреси в білдері, деплоїв попереднього перегляду.

Чи можна використати маску в Redirect URLs у Supabase?

Для попереднього перегляду, обмежену вашою командою чи проєктом, як у прикладах самого Supabase. Ніколи таку, що підходить до адрес, які можуть створити інші, наприклад до всіх застосунків на vercel.app чи lovable.app. Для сайту в продакшні Supabase радить точну адресу.

Автор

Vlad Tkachenko

Засновник Reeve

Я щодня дивлюся на застосунки, зібрані в Lovable, Bolt, v0, Cursor і Replit, і на короткий список помилок, які трапляються в них знову і знову.

Більше про автора

Читати далі

Усі статті

Зробіть резервну копію бази Supabase перед наступною зміною

Reeve за розкладом зберігає зашифровану копію і відновить її за вас, якщо щось зламається.

Резервні копії Supabase

Автоматична зовнішня перевірка, а не повний аудит. Відсутність знахідок не є гарантією безпеки.