Middleware Session Fix
Source: `docs/architecture/MIDDLEWARE_FIX.md`View on GitHub
Middleware Session Fix
Date: November 23, 2025
Issue: Users being redirected to /crm/login on every route navigation despite being logged in
Last verified against code: 2025-02-03
Problem
The middleware was not properly maintaining Supabase session cookies across requests, causing:
- Session cookies not being updated in responses
- Users appearing unauthenticated on each new route
- Constant redirects to login page
Root Causes
1. Cookie Handling Issue
The middleware was creating a new NextResponse object in the cookie set and remove methods, which would discard the previous response and its cookies.
Before (Broken):
set(name: string, value: string, options: CookieOptions) {
request.cookies.set({ name, value, ...options })
response = NextResponse.next({ // ❌ Creates NEW response, discards old cookies
request: { headers: request.headers },
})
response.cookies.set({ name, value, ...options })
}
After (Fixed):
set(name: string, value: string, options: CookieOptions) {
request.cookies.set({ name, value, ...options })
// ✅ Update existing response instead of creating new one
response.cookies.set({ name, value, ...options })
}
2. Incomplete Route Protection
The /intake and /crm routes were not included in middleware protection, causing:
- Session not being refreshed when accessing these routes
- Inconsistent auth state when navigating between routes
Changes Made
1. Fixed Cookie Handling in Middleware
File: middleware.ts
export async function middleware(request: NextRequest) {
let response = NextResponse.next({
request, // Simplified
})
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return request.cookies.get(name)?.value
},
set(name: string, value: string, options: CookieOptions) {
// Update request cookies
request.cookies.set({ name, value, ...options })
// Update response cookies (don't create new response)
response.cookies.set({ name, value, ...options })
},
remove(name: string, options: CookieOptions) {
request.cookies.set({ name, value: '', ...options })
response.cookies.set({ name, value: '', ...options })
},
},
}
)
// This call will now properly update cookies in response
const { data: { user } } = await supabase.auth.getUser()
// ... rest of middleware
}
2. Added Missing Routes to Protection
Added routes:
/intake(staff version) - Now protected at middleware level/crm/*(dashboard and related routes) - Now consistently protected
Excluded routes:
/intake/self-service- Remains public for future use/crm/login- Login page/crm/logout- Logout route
const isCRMRoute = request.nextUrl.pathname.startsWith('/resources') ||
request.nextUrl.pathname.startsWith('/clients') ||
request.nextUrl.pathname.startsWith('/referrals') ||
(request.nextUrl.pathname.startsWith('/intake') &&
!request.nextUrl.pathname.startsWith('/intake/self-service')) ||
(request.nextUrl.pathname === '/crm' ||
request.nextUrl.pathname.startsWith('/crm/'))
Related Files
middleware.ts- Main middleware with fixeslib/supabase-server.ts- Server-side Supabase clientlib/supabase-client.ts- Client-side Supabase clientapp/crm/login/page.tsx- Login page- Access Control
Status: ✅ Fixed
Impact: All CRM routes now maintain session properly
Use links in each imported doc to open its source.