Hi all
I have a few sites ive made in nextJS (using AI)
And these 2 the last person signed in their session shows in the headers of the sites to anyone not logged in
Ive gone through every AI help topic i could find but i just cant seem to stop the session from returning back from the server into the cookies and the logout function doesnt work it keeps returning a logged session
ive gone mental with netlify toml headers and i just can’t seem to understand how this worls
Sorry im not an actual developer i use AI in VSCODE and i can get most things done but this one has been a week now and i cant resolve it
Example of one toml
[[scheduled-functions]]
function = “cleanup-sessions”
schedule = “@daily”
Default cache control for all pages
[[headers]]
for = “/*”
[headers.values]
Cache-Control = “no-store, private, max-age=0, must-revalidate”
Pragma = “no-cache”
Expires = “0”
Surrogate-Control = “no-store”
Vary = “Cookie, Authorization, Accept-Encoding”
Netlify-Vary = “cookie=next-auth.session-token”
Public pages cache control
[[headers]]
for = “/:placeholder(|jobs|directory/|blog/)”
[headers.values]
Cache-Control = “public, no-store, max-age=0, must-revalidate”
Pragma = “no-cache”
Expires = “0”
Surrogate-Control = “no-store”
Vary = “*”
Authenticated routes cache control
[[headers]]
for = “/:section(employers|jobseeker)/*”
[headers.values]
Cache-Control = “private, no-cache, no-store, max-age=0, must-revalidate”
Pragma = “no-cache”
Expires = “0”
Surrogate-Control = “no-store”
Vary = “Cookie, Authorization, Accept-Encoding”
Netlify-Vary = “cookie=next-auth.session-token”
X-Cache-Policy = “auth-route-strict”
Netlify functions cache control
[[headers]]
for = “/.netlify/functions/*”
[headers.values]
Cache-Control = “private, no-cache, no-store, max-age=0, must-revalidate”
Pragma = “no-cache”
Expires = “0”
Surrogate-Control = “no-store”
Vary = “Cookie, Authorization, Accept-Encoding”
Netlify-Vary = “cookie=next-auth.session-token”
API routes cache control
[[headers]]
for = “/api/*”
[headers.values]
Cache-Control = “private, no-cache, no-store, max-age=0, must-revalidate”
Pragma = “no-cache”
Expires = “0”
Surrogate-Control = “no-store”
Vary = “Cookie, Authorization, Accept-Encoding”
Netlify-Vary = “cookie=next-auth.session-token”
Auth endpoints cache control
[[headers]]
for = “/api/auth/*”
[headers.values]
Cache-Control = “private, no-cache, no-store, max-age=0, must-revalidate”
Pragma = “no-cache”
Expires = “0”
Surrogate-Control = “no-store”
Vary = “Cookie, Authorization, Accept-Encoding”
Netlify-Vary = “cookie=next-auth.session-token”
X-Cache-Policy = “auth-api-strict”
Session endpoint - extra strict no-cache
[[headers]]
for = “/api/auth/session”
[headers.values]
Cache-Control = “private, no-cache, no-store, max-age=0, must-revalidate”
Pragma = “no-cache”
Expires = “0”
Surrogate-Control = “no-store”
Vary = “Cookie, Authorization, Accept-Encoding, *”
Netlify-Vary = “cookie=next-auth.session-token”
X-Cache-Policy = “session-api-strict”
X-No-Cache = “true”
Login pages cache control
[[headers]]
for = “/auth/login*”
[headers.values]
Cache-Control = “private, no-cache, no-store, max-age=0, must-revalidate”
Pragma = “no-cache”
Expires = “0”
Surrogate-Control = “no-store”
Vary = “*”
X-Cache-Policy = “login-page-strict”
Special handling for logout
[[redirects]]
from = “/api/auth/logout”
to = “/.netlify/builders/api/auth/logout”
status = 200
force = true
Special handling for auth session
[[redirects]]
from = “/api/auth/session”
to = “/.netlify/builders/api/auth/session”
status = 200
force = true
Special handling for pages with logout parameter
[[redirects]]
from = “/*?logout=”
to = “/api/auth/logout”
status = 200
force = true
– and middleware:
import { NextResponse } from ‘next/server’;
import type { NextRequest } from ‘next/server’;
import { jwtVerify } from ‘jose’;
import { getToken } from ‘next-auth/jwt’;
// Helper function to create a redirect response with cache control headers
function createRedirectResponse(url: URL): NextResponse {
const redirectResponse = NextResponse.redirect(url);
redirectResponse.headers.set(‘Cache-Control’, ‘no-store, max-age=0, must-revalidate’);
redirectResponse.headers.set(‘Pragma’, ‘no-cache’);
redirectResponse.headers.set(‘Expires’, ‘0’);
redirectResponse.headers.set(‘Surrogate-Control’, ‘no-store’);
return redirectResponse;
}
export async function middleware(request: NextRequest) {
// Check if this is a public page that should never have auth cookies
const isPublicPage =
request.nextUrl.pathname === ‘/’ ||
request.nextUrl.pathname === ‘/jobs’ ||
request.nextUrl.pathname.startsWith(‘/directory’) ||
request.nextUrl.pathname.startsWith(‘/blog’) ||
request.nextUrl.pathname.startsWith(‘/about’) ||
request.nextUrl.pathname.startsWith(‘/auth/’) ||
(request.nextUrl.pathname.startsWith(‘/employers/’) &&
(request.nextUrl.pathname.includes(‘/login’) ||
request.nextUrl.pathname.includes(‘/signup’))) ||
(request.nextUrl.pathname.startsWith(‘/jobseeker/’) &&
(request.nextUrl.pathname.includes(‘/login’) ||
request.nextUrl.pathname.includes(‘/signup’)));
// For public pages, create a clean response with no auth cookies
if (isPublicPage && !request.cookies.has(‘_vercel_no_cookie’)) {
const response = NextResponse.next();
// Add a special cookie to prevent infinite loops
response.cookies.set('_vercel_no_cookie', '1', {
maxAge: 10,
path: '/',
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax'
});
// Set strict cache control headers
response.headers.set('Cache-Control', 'no-store, private, max-age=0, must-revalidate');
response.headers.set('Pragma', 'no-cache');
response.headers.set('Expires', '0');
response.headers.set('Vary', 'Cookie, Authorization');
// Clear any auth cookies that might have been set
response.cookies.delete('next-auth.session-token');
response.cookies.delete('next-auth.callback-url');
response.cookies.delete('next-auth.csrf-token');
response.cookies.delete('__Host-next-auth.csrf-token');
response.cookies.delete('__Secure-next-auth.callback-url');
return response;
}
// For all other responses, add cache control headers
const response = NextResponse.next();
// Add strict cache control headers to all responses
response.headers.set(‘Cache-Control’, ‘no-store, private, max-age=0, must-revalidate’);
response.headers.set(‘Pragma’, ‘no-cache’);
response.headers.set(‘Expires’, ‘0’);
response.headers.set(‘Surrogate-Control’, ‘no-store’); // Specific for Netlify CDN
response.headers.set(‘Vary’, ‘Cookie, Authorization, Accept-Encoding, *’);
// Add a unique header to prevent caching based on user
response.headers.set(‘X-User-Unique’, crypto.randomUUID());
// Add a timestamp to ensure the response is always different
response.headers.set(‘X-Response-Time’, Date.now().toString());
response.headers.set(‘X-Cache-Control-Debug’, ‘middleware-default’);
// Check if this is a logout request (has logout query parameter)
const isLogout = request.nextUrl.searchParams.has(‘logout’);
if (isLogout) {
// For logout requests, add extremely strict cache control headers
response.headers.set(‘Cache-Control’, ‘no-store, no-cache, max-age=0, must-revalidate’);
response.headers.set(‘Pragma’, ‘no-cache’);
response.headers.set(‘Expires’, ‘0’);
response.headers.set(‘Surrogate-Control’, ‘no-store’);
response.headers.set(‘X-Cache-Control-Debug’, ‘middleware-logout’);
// Clear all possible session cookies
response.cookies.delete('next-auth.session-token');
response.cookies.delete('next-auth.callback-url');
response.cookies.delete('next-auth.csrf-token');
response.cookies.delete('__Host-next-auth.csrf-token');
response.cookies.delete('__Secure-next-auth.callback-url');
response.cookies.delete('__Secure-next-auth.session-token');
// Clear cookies with standard path (Next.js doesn't support custom paths in delete)
response.cookies.delete('next-auth.session-token');
response.cookies.delete('next-auth.callback-url');
response.cookies.delete('next-auth.csrf-token');
response.cookies.delete('__Host-next-auth.csrf-token');
response.cookies.delete('__Secure-next-auth.callback-url');
response.cookies.delete('__Secure-next-auth.session-token');
// Set special headers to indicate this is a logout response
response.headers.set('X-Logout-Response', 'true');
response.headers.set('X-Logout-Time', Date.now().toString());
}
// Skip middleware for auth callbacks and API routes
if (
request.nextUrl.pathname.startsWith(‘/api/auth’) ||
request.nextUrl.pathname.includes(‘/callback’)
) {
return response;
}
// Handle admin routes
if (request.nextUrl.pathname.startsWith(‘/management’)) {
const token = request.cookies.get(‘admin_token’)?.value;
// Allow access to login page
if (request.nextUrl.pathname === '/management/secure-login') {
return response;
}
// Redirect to login if no token
if (!token) {
return createRedirectResponse(new URL('/management/secure-login', request.url));
}
try {
const secret = new TextEncoder().encode(process.env.ADMIN_JWT_SECRET);
await jwtVerify(token, secret);
return response;
} catch (error) {
console.error('Admin token verification failed:', error);
// Clear the invalid token
const redirectResponse = createRedirectResponse(new URL('/management/secure-login', request.url));
redirectResponse.cookies.set({
name: 'admin_token',
value: '',
expires: new Date(0),
path: '/',
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax'
});
return redirectResponse;
}
}
// Handle protected routes based on role
if (request.nextUrl.pathname.startsWith(‘/jobseeker’) ||
request.nextUrl.pathname.startsWith(‘/employers’)) {
// Determine the required role for this route
const requiredRole = request.nextUrl.pathname.startsWith('/employers') ? 'employer' : 'jobseeker';
// Skip auth check for public routes
const isPublicRoute =
request.nextUrl.pathname.includes('/login') ||
request.nextUrl.pathname.includes('/signup') ||
request.nextUrl.pathname.includes('/reset-password') ||
(requiredRole === 'employer' && (
request.nextUrl.pathname.includes('/claim-profile') ||
// Only allow specific employer profile pages (/employers/123) but not sub-routes
request.nextUrl.pathname.match(/^\/employers\/[a-zA-Z0-9-]+$/) ||
// Allow admin access to employer profile
(request.nextUrl.pathname === '/employers/profile' && request.nextUrl.searchParams.has('admin-access'))
));
if (isPublicRoute) {
return response;
}
try {
const session = await getToken({
req: request,
secret: process.env.NEXTAUTH_SECRET
});
if (!session) {
// Redirect to the appropriate login page
const loginUrl = requiredRole === 'employer'
? `/auth/login?role=employer&redirect=${encodeURIComponent(request.nextUrl.pathname)}`
: `/auth/login?redirect=${encodeURIComponent(request.nextUrl.pathname)}`;
return createRedirectResponse(new URL(loginUrl, request.url));
}
// Make sure session has a role property
if (!session.role) {
console.error('Session missing role property:', session);
const loginUrl = requiredRole === 'employer'
? `/auth/login?role=employer&error=missing_role&redirect=${encodeURIComponent(request.nextUrl.pathname)}`
: `/auth/login?error=missing_role&redirect=${encodeURIComponent(request.nextUrl.pathname)}`;
return createRedirectResponse(new URL(loginUrl, request.url));
}
// Special case for jobseeker profile pages - allow both roles to view
if (request.nextUrl.pathname.startsWith('/jobseeker/profile/')) {
// Allow both roles to access profile pages
if (session.role !== 'jobseeker' && session.role !== 'employer') {
return createRedirectResponse(new URL('/auth/login', request.url));
}
} else {
// For all other protected routes, check role match
if (session.role !== requiredRole) {
// Redirect to the appropriate login page
const loginUrl = requiredRole === 'employer'
? `/auth/login?role=employer&redirect=${encodeURIComponent(request.nextUrl.pathname)}`
: `/auth/login?redirect=${encodeURIComponent(request.nextUrl.pathname)}`;
return createRedirectResponse(new URL(loginUrl, request.url));
}
}
return response;
} catch (error) {
console.error('Session verification failed:', error);
// Redirect to the appropriate login page
const loginUrl = requiredRole === 'employer'
? `/auth/login?role=employer&error=session`
: `/auth/login?error=session`;
return createRedirectResponse(new URL(loginUrl, request.url));
}
}
return response;
}
export const config = {
matcher: [
‘/((?!_next/static|_next/image|favicon.ico|images|fonts|api/auth/callback).*)’,
]
};