Session Handling
Protect routes and access session data in server and client components.
Internal pages (/admin/*) are protected at the proxy/middleware level. This means authentication is checked before any page code executes - but for other routes, please always authenticate users 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-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
Below is a summary of the functions and their use cases:
| Pattern | Use Case | Location |
|---|---|---|
getSession() | Get session data | Server components, server actions |
getAccountContext() | Get user context with redirect | Protected pages |
requireActiveOrganizationId() | Require org context | Org-only pages |
requireAdmin() | Require admin role | Admin pages/layouts |
isUserAdmin() | Check admin without redirect | Conditional rendering |
authenticatedActionClient | Protect server actions | Server actions |
authClient.useSession() | Client-side session | Client components |
Next: Personal Accounts →