Czas trwania sesji (kiedy aplikacja wylogowuje)
Jak długo można nie wchodzić do aplikacji, zanim trzeba się zalogować od nowa.
👤 Instrukcja dla użytkownika
Aplikacja wylogowuje dopiero po 90 dniach nieobecności. Licznik zeruje się przy każdym wejściu, więc jeśli klient zagląda do aplikacji choć raz na kwartał, nie zobaczy ekranu logowania w ogóle. To samo dotyczy pracowników w panelu.
Do końca sierpnia 2026 było to 7 dni — stąd znikające sesje u klientów, którzy zarezerwowali kort i wrócili dwa tygodnie później.
Wylogowanie nadal nastąpi, gdy:
- ktoś sam kliknie „Wyloguj",
- minie 90 dni bez otwarcia aplikacji,
- konto zostanie zawieszone albo usunięte.
Osobna sprawa to operacje wrażliwe — zmiana hasła czy adresu e-mail. Tu nadal obowiązuje próg jednej doby: sesja starsza niż dzień musi potwierdzić tożsamość ponownie, nawet jeśli poza tym jest ważna. To celowe i nie zostało wydłużone.
:::warning Urządzenia współdzielone Na komputerze recepcji czy wspólnym tablecie 90 dni to długo. Tam trzeba kończyć pracę przyciskiem Wyloguj, a nie samym zamknięciem okna. :::
🔧 Dokumentacja techniczna
Konfiguracja
Jedno źródło prawdy: SESSION_COOKIE_MAX_AGE w lib/session-cookie.ts
(90 dni w sekundach). Czyta je zarówno ciastko, jak i session.expiresIn
Better Autha w lib/auth.ts, więc wiersz w bazie i ciastko nie mogą się
rozjechać — ciastko dłuższe niż wiersz przepuszcza w middleware użytkownika,
którego lookup sesji zaraz odrzuca; krótsze wylogowuje mimo ważnej sesji.
| Parametr | Wartość | Znaczenie |
|---|---|---|
session.expiresIn | 90 dni | ile sesja żyje bez aktywności |
session.updateAge | 1 dzień (domyślne) | co ile żądanie przesuwa termin ważności |
session.freshAge | 1 dzień (domyślne) | po jakim czasie operacje wrażliwe żądają ponownego logowania |
session.cookieCache.maxAge | 5 minut | jak długo dane użytkownika czyta się z podpisanego ciastka zamiast z bazy |
Sesja jest rolling: przy pierwszym żądaniu po dobie Better Auth ustawia
expiresAt = teraz + 90 dni (api/routes/session.mjs). Liczy się więc czas bez
wchodzenia do aplikacji, a nie czas od zalogowania.
freshAge celowo nie został podniesiony razem z expiresIn — inaczej
trzymiesięczna sesja mogłaby zmienić hasło bez żadnego potwierdzenia.
Dlaczego nie różnicujemy ról
Odnowienie sesji zawsze ustawia globalne expiresIn; hook session.create.before
zostałby nadpisany przy pierwszym przedłużeniu. Do tego maxAge ciasteczka
sesyjnego Better Auth bierze wprost ze statycznego session.expiresIn
(better-auth/dist/cookies), a nie z expiresAt zapisanego wiersza — skrócenie
samego wiersza dla personelu rozjechałoby ciasteczko z sesją, czyli dokładnie ten
przypadek, przed którym ostrzega komentarz przy SESSION_COOKIE_MAX_AGE.
Krótsza sesja dla personelu wymagałaby więc własnego strażnika w panelu, sprawdzającego wiek sesji — świadomie tego nie ma, wszystkie role mają ten sam termin.
:::warning Świadomy kompromis
Oznacza to, że konto ADMIN/BACKOFFICE na współdzielonej przeglądarce w recepcji
zostaje zalogowane tak długo, jak długo ktoś z niej korzysta (updateAge = 1
dzień przesuwa termin przy każdej wizycie). Wcześniejsze 7 dni ograniczało to
okno. Jeśli kiedyś uznamy to za zbyt szerokie, właściwym miejscem jest strażnik
wieku sesji w panelu, nie expiresIn.
:::
Sprzątanie wygasłych sesji
lib/session-cleanup.ts, wpięte w cotygodniowy cron 0 3 * * 1 na workerze
admina (worker-crons.ts). Usuwa wiersze wygasłe ponad 30 dni temu; świeżo
wygasłe zostają, bo expiresAt jest jedynym śladem sesji, która właśnie się
skończyła, i czyta go wsparcie przy zgłoszeniu „wylogowało mnie".
Pułapka, którą to obchodzi: kafelek Liczba logowań na profilu to COUNT(*)
po tabeli session — zwykły DELETE wyzerowałby licznik wszystkim klientom.
Sprzątanie najpierw dopisuje sumę usuwanych wierszy do session_login_archive
(user_id, logins_count, last_login), oba zapytania w jednym db.batch(),
czyli w transakcji. Odczyt w getSessionActivityByUserIds sumuje archiwum z
żywymi sesjami przez UNION ALL. Szczegóły: Historia logowań.
Pliki
| Plik | Rola |
|---|---|
lib/session-cookie.ts | SESSION_COOKIE_MAX_AGE — jedno źródło prawdy |
lib/auth.ts | session.expiresIn w konfiguracji Better Autha |
app/api/auth/mobile-session/route.ts | ciastko przy przekazaniu sesji do aplikacji mobilnej |
lib/session-cleanup.ts | deleteExpiredSessions |
worker-crons.ts | wpięcie w cron tygodniowy |
migrations/0253_create_session_login_archive.sql | tabela archiwum |
Testy
__tests__/lib/session-cleanup.test.ts — na prawdziwym SQLite: próg 30 dni,
zachowanie licznika logowań przez kolejne przebiegi, rozdział per konto.