Raypx

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

ColumnTypeDescription
iduuidPrimary key, auto-generated UUID v7.
tokentextUnique session token (used in cookie).
expires_attimestamptzWhen the session expires.
user_idtextForeign key to user.id, cascade delete.
ip_addresstextIP address at session creation.
user_agenttextBrowser user-agent string.
last_activetimestamptzLast time the session was used.
created_attimestamptzSession creation time.
updated_attimestamptzLast update time (auto-updates).

account Table

The account table stores OAuth-linked credentials and password hashes:

ColumnTypeDescription
iduuidPrimary key, auto-generated UUID v7.
account_idtextProvider-specific account ID.
provider_idtextProvider name (google, github, credential).
user_idtextForeign key to user.id, cascade delete.
access_tokentextOAuth access token.
refresh_tokentextOAuth refresh token.
passwordtextHashed password (for credential accounts).
scopetextOAuth scopes granted.
created_attimestamptzAccount creation time.
updated_attimestamptzLast 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:

packages/auth/src/index.ts
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:

packages/rpc/src/context.ts
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:

src/routes/(dashboard)/route.tsx
const { data: session, isPending } = authClient.useSession()

if (isPending) return <Skeleton />
// The layout renders a sign-out dropdown when session exists

For 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 expiresAt timestamp.
  • Expired sessions are not returned by getSession().
  • The session table includes a last_active column 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.

On this page