Session Management
How sessions are created, stored, and checked.
Raypx uses Better Auth's session system backed by PostgreSQL. Sessions are created on sign-in, stored in the database, and validated on every request to protected routes.
Database Tables
Sessions are stored in two tables defined in packages/database/src/schema/auth.ts:
session Table
| Column | Type | Description |
|---|---|---|
id | uuid | Primary key, auto-generated UUID v7. |
token | text | Unique session token (used in cookie). |
expires_at | timestamptz | When the session expires. |
user_id | text | Foreign key to user.id, cascade delete. |
ip_address | text | IP address at session creation. |
user_agent | text | Browser user-agent string. |
last_active | timestamptz | Last time the session was used. |
created_at | timestamptz | Session creation time. |
updated_at | timestamptz | Last update time (auto-updates). |
account Table
The account table stores OAuth-linked credentials and password hashes:
| Column | Type | Description |
|---|---|---|
id | uuid | Primary key, auto-generated UUID v7. |
account_id | text | Provider-specific account ID. |
provider_id | text | Provider name (google, github, credential). |
user_id | text | Foreign key to user.id, cascade delete. |
access_token | text | OAuth access token. |
refresh_token | text | OAuth refresh token. |
password | text | Hashed password (for credential accounts). |
scope | text | OAuth scopes granted. |
created_at | timestamptz | Account creation time. |
updated_at | timestamptz | Last update time (auto-updates). |
A single user can have multiple account rows -- one for password credentials and additional rows for each linked OAuth provider. The unique index (provider_id, account_id) prevents duplicate provider links.
TanStack Start Cookies Plugin
Raypx uses the tanstackStartCookies() plugin from Better Auth to integrate sessions with TanStack Start's cookie system:
import { tanstackStartCookies } from "better-auth/tanstack-start"
return betterAuth({
plugins: [tanstackStartCookies()],
// ...
})This plugin handles reading and writing session cookies using TanStack Start's built-in cookie API, ensuring sessions work correctly across SSR, client-side navigation, and API routes.
Server-Side Session Check
In RPC context creation, the session is resolved from the request headers:
import { auth } from "@raypx/auth"
export async function createContext({ req }: { req: Request }) {
const session = await auth.api.getSession({
headers: req.headers,
})
return { session }
}
export type Context = Awaited<ReturnType<typeof createContext>>The session object (or null if unauthenticated) is then available to all RPC procedures through context.session.
Protected Routes
The dashboard layout uses authClient.useSession() to check authentication client-side:
const { data: session, isPending } = authClient.useSession()
if (isPending) return <Skeleton />
// The layout renders a sign-out dropdown when session existsFor server-side route protection, use beforeLoad in the route definition:
export const Route = createFileRoute("/dashboard")({
beforeLoad: async ({ context }) => {
const session = await context.orpc.profile.get.query()
if (!session) throw redirect({ to: "/login" })
},
})Session Expiry
Better Auth manages session tokens with configurable expiry. The default behavior:
- Sessions are stored with an
expiresAttimestamp. - Expired sessions are not returned by
getSession(). - The
sessiontable includes alast_activecolumn that tracks usage.
When a user signs out, the session row is deleted. When a password is reset, all sessions for that user are revoked (deleted).
Password Reset Session Revocation
When revokeSessionsOnPasswordReset is true (the default in Raypx), changing a password deletes all active sessions for that user:
emailAndPassword: {
revokeSessionsOnPasswordReset: true,
}This forces the user to sign in again on all devices after a password change.