Introduction
Once a user is signed in, you need to access their session data—name, email, image, role—throughout your app. Auth.js provides different APIs for Server Components, Client Components, and Route Handlers.
Key Concepts
Session access patterns:
auth()- Server Components, Route Handlers, Server ActionsuseSession()- Client Components (requires SessionProvider)getSession()- Deprecated, useauth()instead
Real World Context
You'll access sessions to:
- Display user info in the header
- Conditionally render UI based on auth status
- Include user ID in database queries
- Protect pages and API routes
Deep Dive
Server Components (Recommended)
typescript// app/dashboard/page.tsx import { auth } from '@/auth'; import { redirect } from 'next/navigation'; export default async function Dashboard() { const session = await auth(); if (!session?.user) { redirect('/api/auth/signin'); } return ( <div> <h1>Welcome, {session.user.name}!</h1> <p>Email: {session.user.email}</p> <img src={session.user.image || '/default-avatar.png'} alt="Profile" className="w-16 h-16 rounded-full" /> </div> ); }
Client Components
First, wrap your app in SessionProvider:
typescript// app/layout.tsx import { SessionProvider } from 'next-auth/react'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html> <body> <SessionProvider>{children}</SessionProvider> </body> </html> ); }
Then use the useSession hook:
typescript// components/UserMenu.tsx 'use client'; import { useSession, signIn, signOut } from 'next-auth/react'; export function UserMenu() { const { data: session, status } = useSession(); if (status === 'loading') { return <div className="animate-pulse">Loading...</div>; } if (!session) { return ( <button onClick={() => signIn()}> Sign In </button> ); } return ( <div className="flex items-center gap-4"> <span>Hi, {session.user?.name}</span> <button onClick={() => signOut()}> Sign Out </button> </div> ); }
Route Handlers
typescript// app/api/user/profile/route.ts import { auth } from '@/auth'; export async function GET() { const session = await auth(); if (!session?.user) { return Response.json({ error: 'Unauthorized' }, { status: 401 }); } const profile = await fetchUserProfile(session.user.email!); return Response.json(profile); }
Session Data Structure
typescripttype Session = { user?: { name?: string | null; email?: string | null; image?: string | null; }; expires: string; // ISO date string };
Extending the Session (Adding Custom Data)
typescript// auth.ts export const { handlers, auth } = NextAuth({ // ... providers callbacks: { session({ session, token }) { // Add custom fields to session if (token.sub) { session.user.id = token.sub; session.user.role = token.role as string; } return session; }, jwt({ token, user }) { // Add custom fields to token if (user) { token.role = user.role; } return token; }, }, });
Common Pitfalls
-
Missing SessionProvider:
useSession()returns undefined without the provider wrapper. -
Not handling loading state:
statuscan be 'loading', 'authenticated', or 'unauthenticated'. Always handle loading. -
Assuming session exists: Always check
session?.userwith optional chaining.
Best Practices
- Prefer Server Components: Use
auth()for most cases—it's faster and doesn't require client-side JS - Handle all states: Loading, authenticated, and unauthenticated
- Extend session via callbacks: Don't refetch user data—add what you need to the session
- Cache session checks: Use React's
cache()if callingauth()multiple times per request
Summary
Access sessions with auth() in Server Components and Route Handlers, and useSession() in Client Components (with SessionProvider). Always handle loading and unauthenticated states. Extend the session with callbacks to include custom user data like roles and IDs.