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

Коротко
- Ніщо в браузері не вміє тримати таємницю, тож сховати ключ API означає перенести його туди, де це можливо. Supabase Edge Function це найменший сервер, який ви можете мати.
- Перенесення це чотири кроки. Спершу замініть ключ, який ви вже опублікували, бо копія у вашому старому бандлі лишається читабельною й після того, як ви приберете його звідти.
- Розгорнута function за замовчуванням вимагає токен, і публічний ключ із вашого ж фронтенду цю вимогу задовольняє. Перевірка того, хто саме викликає, це окремий крок, і саме він не дає чужій людині витрачати вашу квоту.
Кожна інструкція про витік ключа API завершується в одному й тому самому місці: перенесіть його на сервер. А далі обривається. У вас лишається застосунок, зібраний у Lovable, Bolt чи Cursor, жодного сервера навколо та речення, яке припускає, що ви вже знаєте, що робити далі.
Ось та частина, яку інструкція за інструкцією лишає поза кадром. Перенесення ключа в Supabase Edge Function прибирає його з вашої сторінки, і саме собою це не заважає чужій людині ним скористатися. Власна документація Supabase пояснює чому: перевірка, яку розгорнута function виконує за замовчуванням, приймає також ваш публічний ключ, а ваш публічний ключ лежить у вашому фронтенді, доступний для копіювання будь-кому. Сховати ключ це проста половина. Про другу половину не пише ніхто: вирішити, кому ваша нова function відповідає.
Уявіть ключ як універсальний ключ від складу. Зараз він приклеєний зсередини до вітрини, де його читає кожен, хто зупиниться. Edge Function це підсобка з віконцем видачі: ключ лягає туди, а відвідувачі просять у віконці замість того, щоб заходити й брати самим. Чи це покращення, залежить від того, кому віконце відчиняється.
Куди покласти ключ API, якщо не у фронтенд?
Будь-куди, де немає браузера. Edge Function це найменший варіант такого місця.
Браузер не вміє тримати таємницю. Усе, що потрібно вашій сторінці для роботи,
завантажує кожен відвідувач, і відвідувач може прочитати все: код, зображення,
значення, вкомпільовані в код. Це не вада вашого конструктора. Це те, чим є
вебсторінка.
Які ключі API безпечні у фронтенді
це довга версія; коротка полягає в тому, що ключ sk_, ключ OpenAI або
секретний ключ Supabase ніколи б не пережив подорожі в браузер.
Supabase Edge Function це невеликий шматок коду, який виконується на машинах Supabase. Вона може читати секрети, які ви задали у проєкті, відповідає за власною веб-адресою, і ваш застосунок викликає її на імʼя. Ви пишете один файл, Supabase його запускає, і немає сервера, який треба орендувати, латати чи тримати ввімкненим.
Замініть уже опублікований ключ, перш ніж щось переносити
Ключ, який сьогодні лежить у вашому фронтенді, уже публічний, і лишається таким після того, як ви приберете його з коду.
Кожен відвідувач, який відкривав ваш сайт, його завантажив. Кожен робот теж, і автоматичні роботи обходять увесь веб у пошуках саме таких рядків. Ваш старий бандл до того ж лишається у вашій історії версій і в кожному кеші, де є копія вашої сторінки. Видалення рядка з файлу, яким ви керуєте, нічого не міняє у значенні, яке вже роздали.
Тож порядок такий: створити новий ключ у постачальника, притримати його хвилину та відкликати старий, щойно function нижче запрацює. Потім перегляньте сторінку рахунків і журнали використання за весь період, коли старий ключ був назовні. Заміна зупиняє те, що буде далі; на те, що вже сталося, вона не впливає. Якщо це був секретний ключ Supabase, порядок роботи має нюанс, який варто прочитати заздалегідь.
Чотири кроки
Зберегти секрет, написати function, розгорнути її, а потім змінити застосунок, щоб він викликав function замість постачальника.
| Крок | У командному рядку | У панелі |
|---|---|---|
| 1. Зберегти секрет | supabase secrets set MY_API_KEY=… | Edge Functions → Secrets, далі Key і Value |
| 2. Написати function | supabase functions new forward-request | Edge Functions → Deploy a new function → Via Editor |
| 3. Розгорнути | supabase functions deploy | Кнопка Deploy під редактором |
| 4. Викликати із застосунку | supabase.functions.invoke('…') | Так само, у коді вашого застосунку |
Усередині function секрет приходить як змінна середовища, тобто іменоване значення, яке код може прочитати, а ніхто ззовні не може. Документація Supabase про секрети Edge Functions має повний довідник, і три деталі в ній економлять годину плутанини:
- Імʼя секрету не може починатися з
SUPABASE_. Цей префікс зарезервовано для значень, які Supabase задає за вас, і його відхиляють і панель, і API. - Новий секрет читається одразу. Після його зміни розгортати function наново не потрібно.
- Локальне та робоче середовища розділені. Локальний стек читає
supabase/functions/.env, а це інше місце, ніж секрети вашого робочого проєкту, тож задайте значення в обох, інакше function працює на вашій машині й падає після розгортання.
Далі видаліть стару змінну з фронтенду. Якщо вона звалася VITE_,
NEXT_PUBLIC_ чи EXPO_PUBLIC_, цей префікс був вказівкою вкомпілювати значення
в бандл, і префікс це вся
історія того, як воно туди потрапило.
Чи означає вимога токена, що викликати можуть лише мої користувачі?
Ні, і це те місце статті, яке варто прочитати двічі.
Supabase вмикає за замовчуванням перевірку під назвою verify_jwt. Вона
дивиться на заголовок Authorization до того, як запуститься ваш код, і
відхиляє запит, якщо там немає нічого дійсного. Звучить як двері, які відчиняють
лише ваші користувачі. Це не так, бо Supabase документує й те, що та сама
перевірка
приймає публічний або секретний ключ
у будь-якому з двох заголовків, задля сумісності зі старішими проєктами, і прямо
додає, що сама по собі перевірка не автентифікує того, хто надсилає лише ключ
API.
Ваш публічний ключ лежить у вашому фронтенді. Це правильно, і так задумано. Але це також означає, що той, хто відкриє ваш сайт, прочитає ключ і надішле його вашій новій function, пройде платформну перевірку та потрапить у ваш код.
На складі verify_jwt це вахтер, який перевіряє, чи є у вас перепустка
відвідувача. Вона є в кожного відвідувача, бо ви самі роздаєте їх на вході.
Людина біля віконця робить іншу роботу, і саме вона питає, який саме ви
відвідувач.
Замкнути function, щоб не вийшов відкритий проксі
Вирішіть, кого ваша function приймає, і запишіть це рішення всередину function.
Supabase дає для цього обгортку, тож це один рядок, а не проєкт. Поставте
auth: 'user', і function прийматиме токен авторизованого користувача та
віддасть вашому коду клієнт бази, уже обмежений правилами Row Level Security
цього користувача:
import { withSupabase } from 'npm:@supabase/server@1'
export default {
fetch: withSupabase({ auth: 'user' }, async (_req, ctx) => {
// ctx.userClaims каже, хто викликає. Відхиляйте тут те, чого не хочете,
// а потім звертайтеся до постачальника із секретом із середовища.
return Response.json({ ok: true })
}),
}
Сторінка Securing Edge
Functions перелічує інші
режими, і два з них важливі для звичайного застосунку. Function, яку викликає
інша машина, а не браузер, бере auth: 'secret' із секретним ключем у заголовку
apikey. Function, яка приймає вебхуки від Stripe чи GitHub, не може взяти
жодного з них, бо ці постачальники не мають вашого токена; вона ставить
verify_jwt = false і перевіряє підпис постачальника всередині обробника.
Хай що ви оберете, свідомо вирішіть, що станеться, коли прийде той, кого ви не чекали. Приклад function, яка може приймати будь-кого, Supabase наводить такий: перевірка стану, а перевірка стану нічого не коштує, коли її викликає чужа людина. Function, яка переадресовує платний виклик API, виставляє вам рахунок за кожен із них.
Чи можна використовувати service_role в Edge Function?
Можна. Function це єдине місце, де секретному ключу Supabase справді є місце.
Вставляти його теж не доведеться. Supabase кладе ключі проєкту в середовище
function, тож код читає їх звідти, а не з секрету, який ви задали. Новіші
проєкти отримують SUPABASE_SECRET_KEYS і SUPABASE_PUBLISHABLE_KEYS, кожен з
яких це невеликий словник іменованих ключів; старіші проєкти отримують
SUPABASE_SERVICE_ROLE_KEY і SUPABASE_ANON_KEY під їхніми початковими
назвами. Що змінилося, коли Supabase перейменував
ключі підкаже, яка пара у вас.
Беріть секретний ключ для роботи, якій справді треба бачити кожен рядок, наприклад для запису в журнал, який користувач не має права редагувати. Для всього, що користувач читає про себе, беріть його власний токен і дайте вашим правилам Row Level Security відфільтрувати, бо саме для цього вони є.
Перевірка з браузера, єдиний справжній доказ
Відкрийте свій робочий сайт, відкрийте вкладку мережі й подивіться, що браузер надсилає насправді.
Дві речі для перевірки:
- Жоден запит не несе ключа. Пройдіть ту частину застосунку, яка раніше
зверталася до постачальника. Вихідний запит має йти на
…supabase.co/functions/v1/ваша-function, і єдиним посвідченням у ньому має бути ваш публічний ключ або токен вашого користувача. - Ключа немає в завантаженому коді. Скористайтеся пошуком браузера по всіх завантажених файлах і вставте перший десяток символів старого ключа. Жодного результату це та відповідь, якої ви хочете.
Лише нова збірка й нове розгортання прибирають значення з бандла. Тож ключ, який досі знаходиться в пошуку, зазвичай означає, що фронтенд пішов раніше, ніж із нього прибрали змінну.
Наше безкоштовне сканування робить другу перевірку ззовні й називає те, що може прочитати у вашому робочому бандлі, зокрема який ключ Supabase ви опублікували. Воно триває близько 20 секунд і не потребує облікового запису: просканувати застосунок.
Як зробити це з Lovable, Bolt чи Replit
Скористайтеся панеллю Supabase. Жодного кроку в терміналі там немає.
Відкрийте проєкт, оберіть Edge Functions на бічній панелі, далі Deploy a new function → Via Editor. Швидкий старт у панелі від Supabase проходить це зі знімками екрана, а серед запропонованих шаблонів є один для переадресації до постачальника ШІ, тобто саме та форма цієї роботи. Розгортання триває від десяти до тридцяти секунд, і далі function працює за власною адресою. Ваш секрет кладеться на сторінку Edge Function Secrets, у тому ж розділі.
Одне застереження, яке варто знати, перш ніж на це покладатися: Supabase пише, що редактор у панелі не має контролю версій, самих версій і відкату, і радить його для швидкої роботи, а не для коду, який має лишитися. Натискання Deploy перезаписує те, що було. Якщо function зрештою робить щось важливе для вас, завантажте її з тієї сторінки й збережіть файл.
Є також версія цього питання у формі Replit, бо Replit має власне сховище секретів, а ваш застосунок запускає там власний сервер. Що насправді роблять Replit Secrets це та версія.
Коли Edge Function це неправильна відповідь
Два випадки, і в обох перенесення коштує роботи й не дає нічого.
Ключ був публічним за задумом. Ключ Stripe pk_live_, публічний ключ
Supabase або ключ anon, публічний токен Mapbox: вони створені жити в браузері,
а захист міститься деінде. Якщо покласти такий за function, додається гак і не
прибирається жоден ризик. Які це
ключі це той перелік.
Ключ можна обмежити вашим власним сайтом. Браузерні ключі Google це звичайний приклад: у консолі Google ви обмежуєте такий ключ своїми доменами, і саме це рішення Google передбачає для цього випадку. Ключ лишається читабельним і перестає бути корисним тому, хто його скопіює.
Усе інше має бути на сервері. У ключа OpenAI чи Anthropic публічного варіанта немає, і тому ключ постачальника ШІ у вашому фронтенді не має налаштування, яке його врятує, і жодна мініфікація не сховає секретний ключ Stripe від того, хто читає вашу сторінку.
Що зробити просто зараз
Що робити
- Спершу замініть ключ. Створіть новий у постачальника, покладіть його в секрет function і відкличте старий, щойно function запрацює.
- Зберігайте секрет у своєму проєкті Supabase, а не в репозиторії й не у змінній
VITE_. Задайте його окремо для локальної розробки та для робочого середовища. - Розгорніть function, яка використовує секрет, і змініть застосунок так, щоб він викликав function на імʼя замість постачальника.
- Додайте перевірку того, хто викликає. Сам по собі
verify_jwtприймає публічний ключ із вашого ж фронтенду, тож чужу людину він не спиняє. - Видаліть стару змінну, зберіть наново й переконайтеся у вкладці мережі браузера, що нічого вихідне не несе ключа.
- Перегляньте рахунки та використання за весь період, коли старий ключ був публічним. Заміна спиняє наступне списання, а не останнє.
Якщо вам зручніше пройтися всім застосунком, а не одним ключем, десятихвилинний чеклист безпеки охоплює це разом з іншим, що варто закрити у щойно запущеному застосунку.
Поширені запитання
Куди покласти ключ API, якщо не у фронтенд?
Будь-куди, де немає браузера. Supabase Edge Function це найменший варіант: ви зберігаєте ключ як секрет у своєму проєкті, пишете невеликий файл, який його використовує, а ваш застосунок викликає цю function на імʼя замість того, щоб звертатися до постачальника напряму. Так само працюють serverless-маршрут у вашого хостера або власний сервер. Спільне в них те, що ключ читається там, де ваш відвідувач його не бачить.
Що таке Supabase Edge Function?
Невеликий шматок коду, який виконується на машинах Supabase, а не в браузері вашого відвідувача. Він відповідає за власною веб-адресою, він може читати секрети, які ви задали у проєкті, і ваш застосунок викликає його через supabase.functions.invoke. Ви пишете один файл, Supabase його запускає, і немає жодного сервера, який треба орендувати чи обслуговувати.
Чи треба замінювати ключ після перенесення?
Так, і зробіть це першим. Ключ, який лежав у вашому фронтенді, завантажив кожен відвідувач і кожен робот, що читав ваш сайт, і прибирання його з коду нічого не міняє в тих копіях. Створіть новий ключ у постачальника, покладіть його в секрет своєї Edge Function і відкличте старий. Потім перегляньте рахунки та використання за весь період, коли старий ключ діяв.
Чи може будь-хто викликати мою Edge Function?
За замовчуванням function відхиляє запит узагалі без токена, але ця перевірка слабша, ніж здається. Supabase документує, що платформна перевірка приймає також ваш публічний або секретний ключ, а ваш публічний ключ лежить у вашому фронтенді, де його може прочитати будь-хто. Тобто перевірка зупиняє порожній запит, а не рішучий. Щоб приймати лише ваших авторизованих користувачів, перевіряйте того, хто викликає, усередині function.
Чи можна використовувати service_role в Edge Function?
Можна. Edge Function це єдине місце, де секретному ключу Supabase справді є місце, і Supabase сам кладе ключі проєкту в середовище function, тож вам ніколи не доводиться його вставляти. Беріть його для роботи, яка має бачити кожен рядок, а для всього, що має дотримуватися ваших правил Row Level Security, беріть токен того, хто викликає.
Чи можна зробити це без термінала?
Можна. У панелі Supabase є розділ Edge Functions, де ви пишете function у браузері, тиснете Deploy і задаєте свій секрет на сторінці Edge Function Secrets. Supabase попереджає, що редактор у панелі не веде історії версій, тож радить його для швидкої роботи, а командний рядок для того, що ви плануєте зберегти.