Основи безпеки
Supabase "Auth session missing!": куди поділася сесія
"Auth session missing!" означає, що supabase-js не знайшла жодної сесії й навіть не спитала Supabase. Куди поділася сесія і як це виправити.

Коротко
- "Auth session missing!" (AuthSessionMissingError, статус 400) походить із supabase-js у вашому застосунку. Бібліотека шукала сесію, не знайшла жодної й так і не надіслала запит до Supabase.
- На сторінці, де ніхто не входив, це правильна відповідь, і вашому коду треба лише її обробити. На сторінці скидання пароля це означає, що посилання тут нікого не впустило. У Edge Function чи на сервері сесію відвідувача так і не передали.
- Лишіть getUser() на місці. Якщо на сервері замінити його на getSession(), помилка зникне, бо код повірить усьому, що написано в cookie.
Ваш застосунок на Lovable чи Bolt впускає людей через Supabase. Одного ранку ви відкриваєте консоль браузера й бачите червоним ось це, на сторінці, де все виглядає нормально:
AuthSessionMissingError: Auth session missing!
Або клієнтка пише, що сторінка «Задайте новий пароль» щоразу відмовляє їй саме цими словами.
Пошукайте «auth session missing supabase», і знайдете обговорення, повні
порад: замінити getUser() на getSession(), відкотити версію бібліотеки,
додати затримку, перезапустити проєкт. Усі вони виходять із того, що між вашим
застосунком і Supabase щось зламалося. Жодного запиту не було. Помилку пише
бібліотека Supabase всередині вашого застосунку, ще до того, як щось вийде з
браузера.
Що означає "Auth session missing!" у Supabase?
supabase-js спитали, хто ввійшов; вона пошукала сесію, не знайшла жодної й відповіла сама.
Сесія це те, що Supabase Auth видає людині під час входу: пара токенів, які кажуть, хто вона, і які браузер зберігає, щоб наступне завантаження сторінки все ще її впізнавало. supabase-js це бібліотека, через яку ваш застосунок розмовляє із Supabase, і кожен виклик, якому потрібна людина, починається з того, що вона дістає цю сесію.
Повідомлення записане в самій supabase-js, у файлі errors.ts бібліотеки
автентифікації: AuthSessionMissingError з текстом Auth session missing! і
статусом 400. Ці 400 бібліотека ставить собі сама. Жоден сервер так не
відповідав, і журнали Supabase нічого не покажуть, бо getUser() шукає сесію
раніше, ніж робить будь-що інше. Коментар у її коді каже це прямо: вона повертає
помилку, якщо немає ні токена доступу, ні власного заголовка Authorization.
Уявіть рецепцію, яка може сказати, хто з відвідувачів хто. Кожен, хто входить, отримує біля дверей бейдж, і supabase-js тримає цей бейдж у кишені браузера. Коли ваш код питає «хто це?», supabase-js спершу лізе в кишеню. Якщо там є бейдж, вона несе його на рецепцію й повертається з іменем. Якщо кишеня порожня, вона навіть не йде на рецепцію. Вона одразу повертається й каже, що бейджа немає.
Її повертають, а не кидають
getUser() повертає помилку поруч із порожнім користувачем, і що буде далі,
вирішує ваш код.
Виклик виглядає так, і без сесії user дорівнює null, а в error лежить
AuthSessionMissingError:
const { data: { user }, error } = await supabase.auth.getUser()
Нічого не кидається, тож try … catch навколо виклику нічого не ловить. Звідси
й видима шкода, в одній із двох форм:
- Код показує будь-яку
error, яку отримав. Відвідувач, який ще не ввійшов, бачить «Auth session missing!» у сповіщенні чи червоній рамці, на сторінці, яку йому можна було бачити. - Код використовує користувача без перевірки. Він читає
user.id, і сторінка падає зCannot read properties of null, що схоже на зовсім іншу помилку.
Обидва випадки це та сама хиба: «ніхто не ввійшов» сприймають як збій. Спершу перевірте користувача й покажіть відвідувачеві без входу екран, призначений для нього:
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
// ніхто не ввійшов: показати екран входу або публічну сторінку
return
}
Якщо хочете відрізнити цей випадок від справжнього збою, бібліотека називає його
на імʼя: error.name дорівнює 'AuthSessionMissingError'.
Де губиться сесія
У трьох місцях, і лише перше нешкідливе.
1. У цьому браузері ніхто не входив. Ваш застосунок викликає getUser() на
сторінці, яку може відкрити будь-хто, скажімо, щоб вирішити, показувати «Увійти»
чи «Мій обліковий запис». Для відвідувача порожня кишеня це правдива відповідь.
Саме тут більшість і зустрічає це повідомлення, і виправлення коду вище це все,
що потрібно.
2. Сторінка скидання пароля чи магічного посилання. Новий пароль задає
updateUser({ password }), і
посібник Supabase про паролі
чітко визначає порядок: новий пароль збирають і викликають updateUser уже
тоді, коли сесія є. На цій сторінці сесія має прийти з посилання в листі, яке
впускає людину, коли відкривається. Коли цього не стається, кишеня порожня, і
updateUser() відповідає «Auth session missing!». Звичайні причини:
- Посилання вже використали або воно протерміноване ще до того, як його відкрили. Чому Supabase пише, що посилання з листа недійсне або протерміноване розбирає кожну.
- Його відкрили в іншому браузері чи застосунку, ніж той, що його запросив. Коли застосунок використовує вхід PKCE від Supabase, частина обміну лишається в браузері, який його почав, і за документацією Supabase обмін коду треба запускати в тому самому браузері й на тому самому пристрої, де почався процес.
- Воно відправило людину кудись інде, наприклад на
localhost, тож сесія потрапила на адресу, яка не є вашим застосунком. Чому Supabase перенаправляє на localhost розбирає цей випадок.
3. Ваш сервер чи Edge Function так і не отримали сесії. Бейдж лежить у
браузері вашого відвідувача. Код, що працює деінде, Edge Function від Supabase
чи серверна половина застосунку Next.js, зробленого у v0 чи Cursor, створює
власний клієнт Supabase, а новий клієнт починає з порожньою кишенею. Доки йому
не передадуть сесію відвідувача, getUser() там нічого не знаходить і відповідає
«Auth session missing!», не питаючи Supabase.
У Edge Function сесія їде в заголовку Authorization запиту, і supabase-js
використовує її, лише якщо ви передасте її під час створення клієнта. У
застосунку Next.js вона їде в cookie, які серверний помічник Supabase читає,
лише якщо ви дасте йому cookie запиту. Якщо Edge Function повертає цей збій як
помилку, ваш застосунок показує
"Edge Function returned a non-2xx status code",
а в журналі функції на вас чекає це повідомлення.
Як зрозуміти, який у вас випадок
Подивіться, де зʼявляється повідомлення, а тоді що браузер насправді надіслав.
У консолі браузера, на сторінці, яку може відкрити будь-хто. Випадок 1.
Відкрийте інструменти розробника в браузері (F12), виберіть Application,
далі Local Storage і адресу вашого застосунку. supabase-js тримає сесію під
ключем, складеним із sb-, ідентифікатора вашого проєкту й -auth-token. Якщо
такого запису немає, у цього браузера немає сесії, і повідомлення каже правду.
Застосунок Next.js із серверними помічниками Supabase тримає сесію в cookie,
тож шукайте ту саму назву в Cookies.
На сторінці скидання пароля чи магічного посилання. Випадок 2. Подивіться, на якій адресі відкрилося посилання. Якщо це не ваш застосунок або це посилання, яке ви чи поштовий сканер уже відкривали, сесія навіть не мала шансу прийти.
У журналі Edge Function або вашого сервера. Випадок 3. Відкрийте вкладку
Network, повторіть дію й клацніть запит до вашої функції. Якщо в його
заголовку Authorization довгий рядок, що починається з eyJ, браузер надіслав
токен людини, а ваша функція не передала його своєму клієнту.
Чого ви не знайдете на жодному шляху, то це запиту до /auth/v1/user, який
повернувся з цим повідомленням. Якщо бачите невдалий запит, це інша помилка.
Відповідь invalid claim: missing sub claim означає, що ключ вашого проєкту
надіслали туди, де мала бути людина, і та стаття
є двійником цієї.
Виправлення для кожного випадку
Дайте кожному випадку сесію, якої бракує, або відповідь, якій сесія не потрібна.
Випадок 1, відвідувач без входу: перевірте user, перш ніж його
використовувати, як у коді вище, і покажіть екран для того, хто не входив.
Випадок 2, сторінка скидання пароля: дочекайтеся, поки посилання впустить
людину, і лише тоді показуйте форму. Supabase надсилає подію
PASSWORD_RECOVERY, коли хтось приходить за посиланням відновлення, і його
довідник показує сторінку, яка на неї чекає:
supabase.auth.onAuthStateChange((event) => {
if (event === 'PASSWORD_RECOVERY') {
// посилання впустило людину: тепер показати форму нового пароля
}
})
Якщо подія так і не прийде, посилання не спрацювало. Скажіть про це й запропонуйте надіслати нове, а не показуйте форму, яка напевно провалиться.
Випадок 3, функція чи сервер: передайте сесію відвідувача клієнту, якого створюєте. У Edge Function створіть його з вхідним заголовком:
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_ANON_KEY')!,
{ global: { headers: { Authorization: req.headers.get('Authorization')! } } },
)
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return new Response(JSON.stringify({ error: 'Sign in first' }), {
status: 401,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
})
}
Новіша обгортка Supabase, withSupabase({ auth: 'user' }), робить те саме ще до
запуску вашого обробника, а
стаття про Edge Functions показує
налаштування. Лишіть 401 для того, хто викликає без сесії, щоб запит зі сторінки
без входу отримував чітку відмову. Коли заголовок передано, getUser() там може
повернути для такого виклику invalid claim: missing sub claim: це той самий
відвідувач, побачений з іншого боку, і 401 покриває обидва випадки.
У застосунку Next.js створіть серверний клієнт помічником @supabase/ssr від
Supabase з cookie запиту, як показує його серверне налаштування, і викликайте
getUser() на цьому клієнті.
Два виправлення, після яких стає гірше
Обидва радять для цього повідомлення, і кожне зупиняє помилку тим, що прибирає перевірку за нею.
Замінити getUser() на getSession(). У браузері це не змінює нічого
важливого: сховище так само порожнє, тож ви не отримуєте ні сесії, ні
користувача, лише вже без повідомлення. На сервері це небезпечно. Серверний
посібник Supabase каже ніколи не довіряти supabase.auth.getSession() у
серверному коді, бо цей метод читає сесію з cookie, не перевіряючи її знову.
Cookie надсилає вам відвідувач, і будь-хто може надіслати будь-яку cookie, яку
захоче. getUser() supabase-js документує як запит до сервера Supabase Auth,
тож його результат справжній. Тримайте його всюди, де відповідь вирішує, що
комусь можна.
Покласти секретний ключ у клієнт, щоб виклик перестав падати. Клієнту з
ключем service_role чи sb_secret_ не потрібна сесія, щоб читати й писати, бо
він повністю оминає правила вашої бази. Функція знову працює, для будь-кого, хто
її викликає, з входом чи без. Якщо конструктор внесе цю зміну в код, що йде в
браузер, ключ поїде разом із ним до кожного відвідувача. Якщо так уже сталося,
спершу замініть його.
Це можна перевірити ззовні. Наше безкоштовне сканування читає JavaScript вашого опублікованого застосунку так, як прочитав би незнайомець, і називає кожен знайдений ключ, зокрема те, чи місце йому там. Це триває близько 20 секунд і не потребує облікового запису: проскануйте свій застосунок.
Щоб перевірка лишилася після наступної зміни
Перевірка того, хто ввійшов, це частина сторінки, яка найлегше зникає, коли ви просите конструктор прибрати якусь помилку, і обидва швидкі виправлення вище її прибирають.
Reeve Monitor щогодини заново сканує ваш опублікований застосунок:
- усі девʼять перевірок щогодини, для трьох застосунків
- лист того дня, коли нове сканування оцінить ваш застосунок гірше за попереднє; секретний ключ у вашому JavaScript сам обмежує оцінку рівнем D
- чи застосунок доступний, кожні 60 секунд
- щомісячний звіт про те, що він бачив
Сервер, який вірить будь-якій cookie, чи функція, що працює із секретним ключем, дозволяє незнайомцю змінювати рядки ваших користувачів. Reeve Care тримає копію вашої бази Supabase з часу до того, як це станеться.
- зашифрована копія за розкладом, у власному сховищі Reeve в ЄС, поза вашим обліковим записом Supabase
- кожна копія перевірена, перш ніж її зарахують, підрахунком рядків у кожній таблиці
- відновлення в один клік, яке зберігає те, що є зараз, перш ніж замінити
- ваші завантажені файли теж, щойно ви приєднаєте доступ до Storage
- усе, що робить Monitor
Що зробити зараз
Що робити
- Читайте «Auth session missing!» як «тут ніхто не ввійшов». Запит так і не вийшов із вашого застосунку.
- Перевірте, чи
userдорівнюєnull, перш ніж його використовувати, і покажіть відвідувачеві без входу призначений для нього екран замість помилки. - На сторінці скидання пароля дочекайтеся події
PASSWORD_RECOVERY, перш ніж показувати форму, і запропонуйте нове посилання, якщо вона так і не прийде. - У Edge Function чи на сервері передайте заголовок
Authorizationабо cookie відвідувача клієнту, якого створюєте, і відповідайте 401, якщо користувача все одно немає. - Тримайте
getUser()на сервері. ТамgetSession()вірить будь-якій cookie, яку йому надішлють.
Якщо ваші сесії існують, а потім зникають, і люди, які були в системі, раптом опиняються поза нею, це інша проблема з власними причинами, і чому користувачів Supabase викидає з системи її розбирає. Десятихвилинний чекліст безпеки охоплює, що ще часто лишається відкритим у щойно запущеному застосунку.
Поширені запитання
Що означає "Auth session missing!" у Supabase?
Це означає, що supabase-js, бібліотеку Supabase у вашому застосунку, спитали про користувача, який увійшов, а сесії, з якою можна було б спитати, у неї не було. Сесія це те, що Supabase Auth видає людині під час входу, і бібліотека тримає її в браузері. Якщо сесії немає, бібліотека до Supabase взагалі не звертається. Вона повертає AuthSessionMissingError зі статусом 400 і користувача null.
Чому я бачу "Auth session missing!", коли ніхто не ввійшов?
Бо це і є відповідь для відвідувача, який не входив. Ваш застосунок викликав getUser() на сторінці, яку може відкрити будь-хто, і supabase-js повідомила, що ніхто не ввійшов. Нічого не зламано. Вашому коду треба лише прочитати користувача як null і показати екран входу замість помилки.
Чому сторінка скидання пароля пише "Auth session missing!"?
Зміна пароля через updateUser() потребує сесії, а на цій сторінці сесія має прийти з посилання в листі. Якщо посилання вже використали чи воно протерміноване, якщо його відкрили в іншому браузері, ніж той, що його запросив, або воно відправило людину на іншу адресу, сесія не прийшла, і updateUser() нема кого змінювати. Дочекайтеся події PASSWORD_RECOVERY, перш ніж показувати форму, і запропонуйте нове посилання, якщо подія так і не прийде.
Це те саме, що "invalid claim: missing sub claim"?
Ні. "Auth session missing!" вирішується всередині вашого застосунку, перш ніж із нього вийде будь-який запит. "invalid claim: missing sub claim" повертає Supabase Auth, який отримав запит і знайшов ключ вашого проєкту там, де мав бути токен людини. Перше означає, що надсилати не було чого. Друге означає, що надіслали не те.
Чи варто використовувати getSession() замість getUser()?
Не для того, щоб прибрати цю помилку. У браузері getSession() знаходить те саме порожнє сховище й не повертає сесії, тож нічого не змінюється, крім того, що зникає повідомлення. На сервері гірше: supabase-js документує, що getSession() читає сесію просто зі сховища, наприклад із cookie запиту, які можуть бути несправжніми, і радить замість нього getUser(), бо той звіряється із Supabase Auth.