Session Handling
Protect routes and access session data in server and client components. Use getSession, requireAdmin, and authenticatedActionClient.
Session Management
How to access and protect user sessions.
Sessions are stored in the database and referenced via HTTP-only cookies. The kit provides multiple utilities for accessing session data and protecting routes based on your use case.
Internal routes (/admin/*) are protected at the proxy/middleware level. For other routes, always check authentication using the getSession() function.
Proxy-Level Protection
The proxy (apps/web/proxy.ts) automatically guards internal routes:
/admin/*- Require admin role/auth/*- Redirects authenticated users to dashboard
For all other routes, always check for authentication using the getSession() function.
Server Component
import { getSession } from '@kit/better-auth/context';export default async function MyPage() { const session = await getSession(); if (!session) { // Handle unauthenticated state return null; }}Server-Side Session Access
Use getSession() to access the current session in server components or server actions:
Server Component
import { getSession } from '@kit/better-auth/context';export default async function MyPage() { const session = await getSession(); if (!session) { // Handle unauthenticated state return null; } return <div>Hello, {session.user.name}</div>;}The function is cached per request via React's cache(), so multiple calls within the same request are efficient.
Session Data Structure
interface Session { user: { id: string; name: string; email: string; image: string | null; emailVerified: boolean; createdAt: Date; updatedAt: Date; role: string | null; }; session: { id: string; userId: string; expiresAt: Date; activeOrganizationId: string | null; };}Account Context
For protected pages that need user context with automatic redirects, use getAccountContext():
apps/web/app/[locale]/(internal)/dashboard/page.tsx
import { getAccountContext } from '@kit/better-auth/context';export default async function DashboardPage() { // Redirects to sign-in if unauthenticated const context = await getAccountContext(); if (context.isOrganization) { return <OrgDashboard orgId={context.activeOrganizationId} />; } return <PersonalDashboard user={context.user} />;}The getAccountContext function returns the following structure:
Account Context Structure
interface ServerAccountContext { isPersonal: boolean; isOrganization: boolean; activeOrganizationId: string | null; user: { id: string; name: string; email: string; image: string | null; createdAt: Date; updatedAt: Date; emailVerified: boolean; };}Require Organization Context
For pages that require an active organization:
Organization-only page
import { requireActiveOrganizationId } from '@kit/better-auth/context';export default async function MembersPage() { // Redirects to /dashboard if not in org context const orgId = await requireActiveOrganizationId(); const members = await loadMembers(orgId); return <MembersList members={members} />;}Admin Protection
Require Admin in Pages
Use requireAdmin() in admin-only pages or layouts:
apps/web/app/[locale]/(internal)/admin/layout.tsx
import { requireAdmin } from '@kit/auth/require-admin';export default async function AdminLayout({ children }) { const admin = await requireAdmin(); return <AdminLayoutWrapper user={admin}>{children}</AdminLayoutWrapper>;}While this is already checked at the proxy level, we re-run the check in the server component to ensure the admin status is still valid and abundance of caution. We recommend doing this in all admin pages, layouts and server actions to ensure the admin status is always valid.
Check Admin Status
Use isUserAdmin() when you need to check admin status without redirecting:
Conditional rendering
import { isUserAdmin } from '@kit/auth/require-admin';export default async function Header() { const isAdmin = await isUserAdmin(); return ( <nav> <Link href="/dashboard">Dashboard</Link> {isAdmin && <Link href="/admin">Admin</Link>} </nav> );}Protected Server Actions
Use authenticatedActionClient to protect server actions:
apps/web/app/[locale]/(internal)/settings/_lib/actions.ts
'use server';import { authenticatedActionClient } from '@kit/action-middleware';import { z } from 'zod';const updateProfileSchema = z.object({ name: z.string().min(1),});export const updateProfileAction = authenticatedActionClient .inputSchema(updateProfileSchema) .action(async ({ parsedInput, ctx }) => { // ctx.user is guaranteed to exist const { user } = ctx; await updateUser(user.id, parsedInput); return { success: true }; });The middleware throws an "Unauthorized" error if the session is invalid.
Client-Side Session
Use authClient.useSession() in client components:
Client component
'use client';import { authClient } from '@kit/better-auth/client';export function UserMenu() { const session = authClient.useSession(); if (session.isPending) { return <Skeleton />; } if (!session.data) { return <SignInButton />; } return <Avatar name={session.data.user.name} />;}Session Hook Return Type
{ data: Session | null; isPending: boolean; error: Error | null;}Summary
| Function | Use Case | Redirects? |
|---|---|---|
getSession() | Get session data in server contexts | No |
requireSession() | Get session or throw error | No (throws) |
getAccountContext() | Get user context for protected pages | Yes (to sign-in) |
requireActiveOrganizationId() | Require org context | Yes (to dashboard) |
requireAdmin() | Require admin role | Yes (to 403) |
isUserAdmin() | Check admin without redirect | No |
authenticatedActionClient | Protect server actions | No (throws) |
authClient.useSession() | Client-side session | No |
When to Use Each Pattern
Use getSession() when:
- You need to check if a user is logged in
- You want to handle unauthenticated state yourself
- You're in a server component or server action
Use getAccountContext() when:
- Building protected pages that should redirect if not authenticated
- You need to know if the user is in personal or organization context
Use requireActiveOrganizationId() when:
- The page requires an active organization
- You want automatic redirect to dashboard if no org is selected
Use requireAdmin() when:
- Building admin-only pages or layouts
- You want automatic redirect to 403 if not an admin
Use authenticatedActionClient when:
- Building server actions that require authentication
- You want automatic error throwing if not authenticated
Frequently Asked Questions
Is getSession() called multiple times per request?
How do I sign out a user?
How long do sessions last?
Can I access the session in middleware?
What happens when a session expires?
Previous: Password Reset ← | Next: Multi-Factor Authentication →