Session Management

Patterns for managing user sessions with JWT and database storage.

Overview#

Session management enables:

  • User state persistence across requests
  • Multi-device session tracking
  • Session revocation and security
  • Activity monitoring

Prerequisites:

  • Authentication system (Clerk, NextAuth, or custom)
  • Database for session storage (optional)

Implementation#

JWT Session Configuration#

1// auth.config.ts 2import { NextAuthConfig } from 'next-auth' 3 4export const authConfig: NextAuthConfig = { 5 session: { 6 strategy: 'jwt', 7 maxAge: 30 * 24 * 60 * 60, // 30 days 8 updateAge: 24 * 60 * 60 // 24 hours 9 }, 10 callbacks: { 11 jwt({ token, user, trigger, session }) { 12 // Initial sign in 13 if (user) { 14 token.id = user.id 15 token.role = user.role 16 token.organizationId = user.organizationId 17 } 18 19 // Update session when trigger is 'update' 20 if (trigger === 'update' && session) { 21 token.name = session.name 22 token.organizationId = session.organizationId 23 } 24 25 return token 26 }, 27 session({ session, token }) { 28 session.user.id = token.id as string 29 session.user.role = token.role as string 30 session.user.organizationId = token.organizationId as string 31 32 return session 33 } 34 } 35}

Database Sessions#

1// auth.ts 2import NextAuth from 'next-auth' 3import { PrismaAdapter } from '@auth/prisma-adapter' 4import { prisma } from '@/lib/db' 5 6export const { handlers, auth, signIn, signOut } = NextAuth({ 7 adapter: PrismaAdapter(prisma), 8 session: { 9 strategy: 'database', 10 maxAge: 30 * 24 * 60 * 60, // 30 days 11 updateAge: 24 * 60 * 60 // Update session every 24 hours 12 }, 13 callbacks: { 14 session({ session, user }) { 15 session.user.id = user.id 16 session.user.role = user.role 17 return session 18 } 19 }, 20 providers: [ 21 // ... providers 22 ] 23})

Session Refresh#

1// lib/auth/session.ts 2import { auth, signIn } from '@/auth' 3import { redirect } from 'next/navigation' 4 5export async function getSession() { 6 const session = await auth() 7 8 if (!session) { 9 return null 10 } 11 12 // Check if session is about to expire 13 const expiresAt = new Date(session.expires) 14 const now = new Date() 15 const daysUntilExpiry = (expiresAt.getTime() - now.getTime()) / (1000 * 60 * 60 * 24) 16 17 if (daysUntilExpiry < 7) { 18 // Session will expire soon, refresh it 19 // This triggers the updateAge callback 20 } 21 22 return session 23} 24 25export async function requireAuth() { 26 const session = await getSession() 27 28 if (!session) { 29 redirect('/login') 30 } 31 32 return session 33} 34 35export async function requireRole(requiredRoles: string[]) { 36 const session = await requireAuth() 37 38 if (!requiredRoles.includes(session.user.role)) { 39 redirect('/unauthorized') 40 } 41 42 return session 43}

Session Update#

1// components/UpdateSessionForm.tsx 2'use client' 3 4import { useSession } from 'next-auth/react' 5import { useState } from 'react' 6 7export function UpdateSessionForm() { 8 const { data: session, update } = useSession() 9 const [name, setName] = useState(session?.user?.name ?? '') 10 11 const handleSubmit = async (e: React.FormEvent) => { 12 e.preventDefault() 13 14 // Update user in database 15 await fetch('/api/user/profile', { 16 method: 'PATCH', 17 body: JSON.stringify({ name }) 18 }) 19 20 // Update session 21 await update({ name }) 22 } 23 24 return ( 25 <form onSubmit={handleSubmit}> 26 <input 27 type="text" 28 value={name} 29 onChange={e => setName(e.target.value)} 30 /> 31 <button type="submit">Update</button> 32 </form> 33 ) 34}

Active Sessions Management#

1// prisma/schema.prisma 2model Session { 3 id String @id @default(cuid()) 4 sessionToken String @unique 5 userId String 6 expires DateTime 7 user User @relation(fields: [userId], references: [id], onDelete: Cascade) 8 9 // Additional fields for session management 10 userAgent String? 11 ipAddress String? 12 lastActive DateTime @default(now()) 13 createdAt DateTime @default(now()) 14 15 @@index([userId]) 16}
1// lib/auth/sessions.ts 2import { prisma } from '@/lib/db' 3import { headers } from 'next/headers' 4 5export async function getUserSessions(userId: string) { 6 return prisma.session.findMany({ 7 where: { userId }, 8 orderBy: { lastActive: 'desc' }, 9 select: { 10 id: true, 11 userAgent: true, 12 ipAddress: true, 13 lastActive: true, 14 createdAt: true, 15 expires: true 16 } 17 }) 18} 19 20export async function revokeSession(sessionId: string, userId: string) { 21 return prisma.session.deleteMany({ 22 where: { 23 id: sessionId, 24 userId // Ensure user owns the session 25 } 26 }) 27} 28 29export async function revokeAllOtherSessions( 30 currentSessionToken: string, 31 userId: string 32) { 33 return prisma.session.deleteMany({ 34 where: { 35 userId, 36 NOT: { sessionToken: currentSessionToken } 37 } 38 }) 39} 40 41export async function updateSessionActivity(sessionToken: string) { 42 const headersList = headers() 43 44 await prisma.session.update({ 45 where: { sessionToken }, 46 data: { 47 lastActive: new Date(), 48 userAgent: headersList.get('user-agent'), 49 ipAddress: 50 headersList.get('x-forwarded-for') ?? 51 headersList.get('x-real-ip') 52 } 53 }) 54}

Session List UI#

1// app/settings/sessions/page.tsx 2import { auth } from '@/auth' 3import { getUserSessions, revokeSession } from '@/lib/auth/sessions' 4import { formatDistanceToNow } from 'date-fns' 5import { UAParser } from 'ua-parser-js' 6 7export default async function SessionsPage() { 8 const session = await auth() 9 if (!session) return null 10 11 const sessions = await getUserSessions(session.user.id) 12 13 return ( 14 <div className="space-y-6"> 15 <h2 className="text-xl font-semibold">Active Sessions</h2> 16 17 <div className="space-y-4"> 18 {sessions.map(s => { 19 const ua = new UAParser(s.userAgent ?? '').getResult() 20 const isCurrent = s.id === session.sessionId 21 22 return ( 23 <div 24 key={s.id} 25 className="flex items-center justify-between rounded-lg border p-4" 26 > 27 <div> 28 <div className="flex items-center gap-2"> 29 <span className="font-medium"> 30 {ua.browser.name} on {ua.os.name} 31 </span> 32 {isCurrent && ( 33 <span className="rounded bg-green-100 px-2 py-0.5 text-xs text-green-800"> 34 Current 35 </span> 36 )} 37 </div> 38 <div className="text-sm text-gray-500"> 39 <span>{s.ipAddress}</span> 40 <span className="mx-2">-</span> 41 <span> 42 Last active {formatDistanceToNow(s.lastActive, { addSuffix: true })} 43 </span> 44 </div> 45 </div> 46 47 {!isCurrent && ( 48 <form action={async () => { 49 'use server' 50 await revokeSession(s.id, session.user.id) 51 }}> 52 <button 53 type="submit" 54 className="text-sm text-red-600 hover:underline" 55 > 56 Revoke 57 </button> 58 </form> 59 )} 60 </div> 61 ) 62 })} 63 </div> 64 </div> 65 ) 66}

Session Middleware#

1// middleware.ts 2import { NextRequest, NextResponse } from 'next/server' 3import { auth } from '@/auth' 4 5export async function middleware(request: NextRequest) { 6 const session = await auth() 7 8 // Check if session exists for protected routes 9 if (request.nextUrl.pathname.startsWith('/dashboard')) { 10 if (!session) { 11 return NextResponse.redirect(new URL('/login', request.url)) 12 } 13 14 // Check session expiry 15 if (new Date(session.expires) < new Date()) { 16 const response = NextResponse.redirect(new URL('/login', request.url)) 17 // Clear invalid session cookie 18 response.cookies.delete('next-auth.session-token') 19 return response 20 } 21 } 22 23 // Add session info to headers for logging 24 const requestHeaders = new Headers(request.headers) 25 if (session?.user) { 26 requestHeaders.set('x-user-id', session.user.id) 27 } 28 29 return NextResponse.next({ 30 request: { headers: requestHeaders } 31 }) 32} 33 34export const config = { 35 matcher: ['/dashboard/:path*', '/api/:path*'] 36}

Best Practices#

  1. Track session metadata - Store user agent, IP, and last active time
  2. Enable session revocation - Allow users to sign out of other devices
  3. Set reasonable expiry - Balance security with user convenience
  4. Update activity timestamps - Track last active time for idle detection
  5. Use secure cookies - httpOnly, secure, sameSite flags
  • JWT - JWT token handling
  • RBAC - Role-based access control
  • MFA - Multi-factor authentication