Session Management
Managing user sessions
Server-Side Session
All server-side auth utilities are available through the auth object.
Get Current Session
import { getAuth } from "@/lib/auth/server";
// Cached session retrieval (recommended)
const auth = await getAuth();
// auth: { user, session } | null
if (auth?.user) {
console.log("Logged in as:", auth.user.email);
console.log("Session expires:", auth.session.expiresAt);
console.log("User role:", auth.user.role);
}Direct API Access
For custom scenarios, use the direct API:
import { auth } from "@/lib/auth/server";
import { headers } from "next/headers";
const session = await auth.api.getSession({
headers: await headers(),
});Layout Guards
Use layout guards to protect routes at the layout level:
import { auth } from "@/lib/auth/server";
import { UserRole } from "@/db/enums";
// Require specific role(s)
const session = await auth.requireRole(UserRole.USER);
const session = await auth.requireRole([UserRole.USER, UserRole.ADMIN]);
// With callback preservation
await auth.requireRole(UserRole.ADMIN, { preserveCallback: true });
// With onboarding required
await auth.requireRole(UserRole.ADMIN, { requireOnboardingCompleted: true });
// Require unauthenticated (for login/register pages)
await auth.requireUnauthenticated();In tRPC Procedures
// Session is automatically available in context
export const myRouter = createTRPCRouter({
protectedRoute: authProcedure.query(async ({ ctx }) => {
// ctx.user is guaranteed to be non-null
const userId = ctx.user.id;
const userEmail = ctx.user.email;
return { userId, userEmail };
}),
});In API Routes
import { getAuth } from "@/lib/auth/server";
import { unauthorized } from "next/navigation";
import { NextRequest } from "next/server";
export async function GET(request: NextRequest) {
const auth = await getAuth();
if (!auth) {
unauthorized();
}
// Proceed with authenticated request
return Response.json({ user: auth.user });
}Client-Side Session
useAuth Hook
The primary hook for accessing auth state on the client:
"use client";
import { useAuth } from "@/lib/auth/client";
export function UserProfile() {
const { user, session, isAuthenticated, signOut } = useAuth();
if (!isAuthenticated) return <LoginPrompt />;
return (
<div>
<h1>Welcome, {user.name}</h1>
<p>Email: {user.email}</p>
<p>Role: {user.role}</p>
<p>Session expires: {session.expiresAt.toISOString()}</p>
<button onClick={() => signOut()}>Logout</button>
</div>
);
}useAuth Return Values
| Property | Type | Description |
|---|---|---|
user | User | null | Current user object |
session | Session | null | Current session |
isAuthenticated | boolean | Whether user is logged in |
signOut | function | Sign out function |
Auth Actions
Sign In
import { authClient } from "@/lib/auth/client";
// Sign in with email/password
await authClient.signIn.email({
email: "user@example.com",
password: "password123",
});
// Sign in with OAuth
await authClient.signIn.social({
provider: "google",
callbackURL: PageDashboard(),
});
// Sign in with magic link
await authClient.signIn.magicLink({
email: "user@example.com",
callbackURL: PageDashboard(),
});Sign Up
await authClient.signUp.email({
email: "user@example.com",
password: "password123",
name: "John Doe",
});Sign Out
await authClient.signOut();Session Freshness
For sensitive operations, require a recent authentication:
// Configure in AuthConfig
sessionFreshAge: 300, // 5 minutes in seconds
// In procedure
export const sensitiveRouter = createTRPCRouter({
deleteAccount: authProcedure.mutation(async ({ ctx }) => {
if (ctx.session.fresh === false) {
throw new TRPCError({
code: "FORBIDDEN",
message: "Please re-authenticate",
});
}
// Proceed with sensitive operation
}),
});Session Storage Options
JWT (Default)
Stateless sessions stored in cookie:
// src/config/app.ts
export const AuthConfig = {
storeSessionInDatabase: false, // Default
};Advantages:
- No database queries
- Fast performance
- Scales horizontally
Disadvantages:
- Cannot revoke immediately
- Larger cookie size
Database Sessions
Stateful sessions with revocation support:
// src/config/app.ts
export const AuthConfig = {
storeSessionInDatabase: true,
};Advantages:
- Immediate session revocation
- Smaller cookie size
- Track active sessions
Disadvantages:
- Database query per request
- Requires database connection
Session Lifecycle
Session Creation
Automatically created on successful authentication:
- User logs in with credentials or OAuth
- Server validates credentials
- Session token generated
- Token stored in cookie
- User redirected to callback URL
Session Refresh
Sessions are automatically refreshed:
// Disable auto-refresh if needed
export const AuthConfig = {
disableSessionRefresh: true,
};Session Expiration
Sessions expire based on configuration:
// Check expiration
if (auth?.session.expiresAt < new Date()) {
// Session expired - redirect to login
}Session Revocation
Manual Sign Out
await authClient.signOut();Revoke All Sessions (Database Mode)
await auth.api.revokeAllSessions({ userId: user.id });Account Management
Change Password
import { FormChangePassword } from "@/features/auth/components";
<FormChangePassword />Change Email
import { FormChangeEmail } from "@/features/auth/components";
<FormChangeEmail currentEmail={user.email} />Delete Account
import { FormDeleteAccount } from "@/features/auth/components";
// Requires typing "DELETE" to confirm
<FormDeleteAccount />Best Practices
1. Always Use Server-Side Checks
// ❌ Bad - Client-only check
if (user?.role === "admin") {
// Can be bypassed
}
// ✅ Good - Server-side check
await auth.requireRole(UserRole.ADMIN);2. Handle Auth Errors
try {
const auth = await getAuth();
if (!auth) throw new Error("Unauthorized");
} catch (error) {
redirect(PageLogin());
}3. Use Layout Guards
// app/[locale]/(admin)/layout.tsx
export default async function Layout({ children }) {
await auth.requireRole(UserRole.ADMIN, { preserveCallback: true });
return <AdminLayout>{children}</AdminLayout>;
}4. Implement Session Freshness
For sensitive operations like account deletion:
if (ctx.session.fresh === false) {
return {
success: false,
message: ctx.t("auth.reAuthRequired"),
};
}