Skip to main content

User Session Architecture with Refresh Tokens

Introduction

This document describes how the platform manages web, guest, and mobile app (JWT) sessions in a unified way using the auth_sessions table. The design provides strong security, clear observability, and user/admin control.

Session Types

TypeIdentifier in auth_sessionsCreation MomentRevivalNotes
Webapp_jti = NULL, may have refresh_token_hashAt web login or from integrationsYes (refresh-token)2 h inactivity timeout
Guestuser_id = NULL, app_jti = NULLAutomatic when browsingNoDeleted on inactivity
Mobileapp_jti ≠ NULL (JWT jti)At JWT issuance (rows pre-created for all current tenants)NoExpires after 1 year

Lifecycle

Creation

  1. Web – created on successful web login. If "remember" is used during the login, a refresh token is generated for the session
  2. Mobile – when the API issues a JWT, the backend:
    • Extracts the token's jti.
    • Enumerates every tenant the user currently belongs to.
    • Inserts one session row per tenant with app_jti = jti.
    • Each row holds last_activity = now().
    • Each row is expirable individually by the user/admins
  3. Guest – created automatically for anonymous browsing.

Expiration

  • Guest sessions and web sessions without refresh token expire after 120 min of inactivity (last_activity comparison).
  • Web sessions with refresh token become inactive after 120 min but can be revived (see Revival section).
  • Mobile sessions expire after 1 year from creation (JWT expiration time).

Revival

Only web sessions that still hold a valid refresh-token can be revived (handled by HandleSessionRefreshToken).
Mobile sessions do not support revival; users should simply obtain a new JWT after a 401 status.

Termination

Sessions can be terminated through several mechanisms:

User-Initiated Termination

  • Direct logout: When users log out, their current session is deleted
  • My Account management: Users can close other active sessions from the "My Account" section

Administrative Termination

  • Auth Sessions API: Admins can terminate user sessions via the auth sessions API endpoints

Mobile App Logout

  • Cross-app logout: When a user logs out from any mobile app (Publica.la Reader or custom tenant apps), all sessions containing that specific app_jti are deleted across all tenants
  • Immediate effect: After deletion, any request using that JWT receives HTTP 401 status

Session Cleanup (auth:purge-sessions)

Runs every hour at minute 25.

  • Web sessions without a refresh token are deleted immediately after they become inactive, as they cannot be revived.
  • Web sessions with a refresh token are purged only if their last_activity is older than the purge threshold (30 days).
  • Mobile sessions: Currently excluded from the cleanup process initially. They persist until manually deleted by users, admins, or app logout.

The logic is decoupled from refresh token expiration to allow future extensibility (e.g., tracking dormant sessions separately).

A --dry-run option is available to simulate purges without deleting any data.

Refresh Token (Web Sessions Only)

Stored securely in the database (refresh_token_hash) and client cookie (session_refresh_token).

Rotation cadence and race-condition mitigation

The refresh token rotates intelligently to avoid race conditions and limit replay opportunities:

  1. CustomDatabaseSessionHandler issues a new refresh token only if: • the _issue_refresh_token session flag is present and
    • the timestamp _last_refresh_rotated_at shows that at least 10 minutes have elapsed since the previous rotation.
  2. When a rotation occurs the handler updates _last_refresh_rotated_at. Concurrent requests read the fresh timestamp and skip their own rotation, preventing double issuance.

This strategy keeps the replay window under 10 minutes, eliminates the race condition, and avoids extra database queries.

Tokens still expire 30 days after their most recent issuance.

Only web sessions with a non-expired refresh token can be revived.

Data Model

Each session is stored in the auth_sessions table with the following key fields:

  • id: Session ID (primary key, matches cookie for web sessions)
  • user_id: Linked user (nullable for guest sessions)
  • last_activity: UNIX timestamp of last activity (Laravel sessions use UNIX instead of datetime)
  • is_active: Indicates if the session is still valid
  • refresh_token_hash: Hashed version of the refresh token (nullable, only for web sessions)
  • refresh_token_expires_at: Token expiration timestamp (only for web sessions)
  • app_jti: JWT identifier for mobile sessions (null for web sessions)
  • user_agent, ip_address, auth_entry_point, etc. for auditing
  • payload: JSON field storing mobile app information (app_name, app_version, os, os_version)

Security Considerations

Refresh Token Rotation: Refresh tokens are rotated on each request to mitigate replay attacks. When an attacker intercepts a refresh token (through network sniffing or XSS), the token becomes useless after the legitimate user makes their next request, as the system generates a new token and invalidates the old one.

Session ID Regeneration: Session IDs are regenerated on login and refresh to prevent session fixation attacks. In this attack, an attacker creates a session ID and tricks a user into authenticating with that known ID (through phishing or URL manipulation). By regenerating the session ID after authentication, we ensure the attacker cannot access the authenticated session even if they know the original ID.

Expired Session Invalidation: Sessions that are expired and not revivable are treated as completely invalid. This prevents session resurrection attacks where an attacker might try to reuse old session data from abandoned or compromised devices. Once a session expires without a valid refresh token, it cannot be restored under any circumstances.

JWT Security: For mobile sessions, JWTs maintain statelessness while server-side tracking provides observability. The app_jti field allows for explicit token revocation when needed.

Domain Placement

Session logic resides in the Identity domain.

CustomDatabaseSessionHandler: Reads, writes, and structures session data.

HandleSessionRefreshToken middleware: Handles revival logic and re-authentication for web sessions.

SingleTenantAuthMiddleware: Handles mobile session tracking during JWT validation. It will also handle creation during the initial grace period

AuthSession model: Encapsulates session behavior and query scopes for both web and mobile sessions.

Session Visibility for Users

My Account Session Management

Users can view and manage their active sessions through the My Account section, which provides a unified interface for all session types:

Session Display

  • Web sessions: Displayed with browser and device information derived from user agent strings
  • Mobile sessions: Shown with "Mobile App" indicators, identified by the presence of app_jti field
  • Session details: Each entry includes last activity time, device/platform information, and IP address when available

User Actions

  • View all active sessions: See sessions across web browsers and mobile applications
  • Terminate individual sessions: Users can close specific sessions (e.g., "log out of other devices")
  • Session information: Access details about when and where each session was created

Visibility Rules

  • User-initiated sessions only: Only web and mobile sessions created by the user are displayed
  • Impersonated sessions excluded: Administrative impersonation sessions are hidden from end users

This unified approach allows users to maintain control over their account security across all platforms and devices.

Session Data Exposure

API Response Structure

The AuthSessionResource exposes session information through two fields for backward compatibility and gradual migration:

  • user_agent_info (deprecated): Maintained for existing API consumers. Not displayed in the public doc.
  • session_data (recommended): New field with identical structure for web, and a new one for mobile.

Both fields are populated from the sessionData attribute in the AuthSession model.

Session Data Attributes

The AuthSession model provides a unified sessionData attribute that returns different structures based on session type:

Web Sessions (app_jti = null)

[
'browser' => 'Chrome', // Parsed from user_agent
'device' => 'Desktop', // Detected device type
'os' => 'macOS', // Detected operating system
'type' => 'web', // Session type identifier
'raw' => 'Mozilla/5.0...' // Original user_agent string
]

Mobile Sessions (app_jti != null)

[
'app_name' => 'Publica Reader', // From payload JSON
'app_version' => '2.1.0', // From payload JSON
'os' => 'iOS', // From payload JSON
'os_version' => '17.2', // From payload JSON
'type' => 'mobile', // Session type identifier
'raw' => 'PublicaReader/2.1.0...' // Original user_agent string
]

X

Graph View