Skip to main content

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.

ParametrWartośćZnaczenie
session.expiresIn90 dniile sesja żyje bez aktywności
session.updateAge1 dzień (domyślne)co ile żądanie przesuwa termin ważności
session.freshAge1 dzień (domyślne)po jakim czasie operacje wrażliwe żądają ponownego logowania
session.cookieCache.maxAge5 minutjak 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

PlikRola
lib/session-cookie.tsSESSION_COOKIE_MAX_AGE — jedno źródło prawdy
lib/auth.tssession.expiresIn w konfiguracji Better Autha
app/api/auth/mobile-session/route.tsciastko przy przekazaniu sesji do aplikacji mobilnej
lib/session-cleanup.tsdeleteExpiredSessions
worker-crons.tswpięcie w cron tygodniowy
migrations/0253_create_session_login_archive.sqltabela archiwum

Testy

__tests__/lib/session-cleanup.test.ts — na prawdziwym SQLite: próg 30 dni, zachowanie licznika logowań przez kolejne przebiegi, rozdział per konto.