b90fa6f619
Security: session hardening Refresh token rotation with theft detection (family_id) - Added family_id column to auth.sessions (migration 20260507000000_session_family.sql) grouping all tokens issued from the same login into a family - On refresh, the new session inherits the parent's family_id - If a revoked token is replayed (indicates the token was stolen after rotation), the entire family is immediately invalidated and a warning is logged — forcing re-authentication on all devices SameSite=Strict on refresh cookie - Access cookie stays SameSite=Lax (needed for top-level navigation) - Refresh cookie upgraded to SameSite=Strict — it is only ever used for explicit POST to /api/auth/refresh, never via cross-site navigation Refresh token TTL: 30 days → 7 days - With rotation, active sessions auto-renew and effectively never expire - Inactive sessions expire after 7 days instead of 30, reducing the theft window
20 lines
630 B
SQL
20 lines
630 B
SQL
-- Add token family tracking to sessions.
|
|
--
|
|
-- family_id groups all refresh tokens issued from the same original login.
|
|
-- When a rotation detects a revoked token being replayed (possible theft),
|
|
-- the entire family is invalidated — forcing re-authentication on all devices
|
|
-- that shared that login event.
|
|
--
|
|
-- Existing sessions are seeded with family_id = id (each is its own family).
|
|
|
|
ALTER TABLE auth.sessions
|
|
ADD COLUMN family_id UUID;
|
|
|
|
UPDATE auth.sessions
|
|
SET family_id = id;
|
|
|
|
ALTER TABLE auth.sessions
|
|
ALTER COLUMN family_id SET NOT NULL;
|
|
|
|
CREATE INDEX idx_sessions_family_id ON auth.sessions(family_id);
|