Skip to main content
CharleOS uses server-side sessions stored in PostgreSQL. This page explains how sessions work and how to manage them.

Session Architecture

Storage

Sessions are stored in the database, not in JWTs: Benefits:
  • Can be revoked instantly
  • Track session metadata (IP, user agent)
  • No token size limits
  • More secure (no client-side storage)
Tables:
  • session - Team member sessions
  • client_session - Client portal sessions

Session Lifecycle

Creation

Sessions are created when a user signs in:
Session Record:

Validation

On every request, the session is validated:
1

Extract Token

Session token read from HTTP-only cookie
2

Database Lookup

Query session table for matching token
3

Check Expiry

Verify expiresAt is in the future
4

Load User

Join with user table to get user data
5

Return Session

Return session object with user data

Refresh

Sessions are automatically refreshed on activity:
How it works:
  • If session is older than 1 day, it’s refreshed
  • updatedAt timestamp updated
  • expiresAt extended by 7 days
  • User stays logged in without re-authentication

Expiry

Sessions expire automatically:
  • Team sessions: 7 days from last activity
  • Client sessions: 30 days from last activity
Cleanup:
  • Expired sessions are periodically purged from database
  • Better Auth handles this automatically

Revocation

Sessions can be revoked (sign out):
What happens:
  1. Session record deleted from database
  2. Cookie cleared
  3. User redirected to sign-in page

Server-Side Session Access

In Server Components

In API Routes

In Server Actions

Client-Side Session Access

Using React Hook

Session Object Structure

Session Security

HTTP-Only Cookies

Session tokens are stored in HTTP-only cookies: Benefits:
  • Not accessible via JavaScript
  • Protected from XSS attacks
  • Automatically sent with requests
  • Secure flag in production (HTTPS only)
Cookie Configuration:

CSRF Protection

Better Auth includes built-in CSRF protection:
  • All state-changing requests require valid CSRF token
  • Tokens are stored in separate cookie
  • Automatically validated on each request

Session Metadata

CharleOS tracks additional session metadata:

Multi-Device Sessions

Users can be signed in on multiple devices:

Revoking All Sessions

Client Portal Sessions

Client portal sessions work identically but:
  • Use client_session table
  • Longer expiry (30 days vs 7 days)
  • Separate cookie (client_auth.session_token)

Session Management UI

Current Session Info

Sign Out Button

Troubleshooting

Session Not Persisting

Symptoms:
  • User signed in but immediately signed out
  • Cookies not being set
Causes:
  1. BETTER_AUTH_URL doesn’t match actual URL
  2. HTTPS/domain mismatch
  3. Browser blocking cookies
Fix:
  1. Verify .env.local has correct URL
  2. Check browser console for cookie warnings
  3. Clear cookies and try again

Session Expired Too Quickly

Symptoms:
  • User signed out before 7 days
Causes:
  • Session not being refreshed
  • updateAge too long
Fix:
  • Check session.updateAge in lib/auth.ts
  • Ensure user is making requests (refreshes session)

Multiple Sign-Outs Required

Symptoms:
  • User signs out but still sees authenticated state
Causes:
  • Client-side cache not cleared
  • SWR still holding session data
Fix:

Best Practices

Never trust client-side session state:
Never store session tokens in localStorage or sessionStorage:
  • Better Auth uses HTTP-only cookies by default
  • Not accessible via JavaScript
  • Protected from XSS
Sessions should expire after inactivity:
Store IP and user agent for security:
  • Helps identify suspicious activity
  • Useful for security audits
  • Better Auth stores this automatically

Authentication

Auth architecture overview

Better Auth

Auth configuration details

Permissions

Role-based access control