Problem Statement
I’m working on a Next.js app where I need to manage user authentication with cookies. I have set up login
, logout
, and middleware
files for handling JWT tokens and securing routes. However, I’m facing an issue where logging out does not always clear the cookie, and it seems inconsistent in detecting or removing the cookie in certain cases. The logout works seamlessly in localhost, but in Vercel deployment it doesn’t work.
Code Setup
Here’s my code for each relevant file:
-
Login Route (
login/route.ts
)- This endpoint creates and sets a JWT token in the cookies when the user logs in.
import { NextResponse, NextRequest } from "next/server"; import jwt from 'jsonwebtoken'; import { cookies } from 'next/headers'; export async function POST(request: NextRequest) { try { const body = await request.json(); const token = jwt.sign(body.user, 'secret', { expiresIn: '1d' }); const isLocalhost = request.url.includes('localhost'); cookies().set('token', token, { httpOnly: true, path: '/', sameSite: 'lax', secure: !isLocalhost, maxAge: 7 * 24 * 60 * 60, // Adjusted for clarity ...(isLocalhost ? {} : { domain: 'swiftship-nine.vercel.app' }) }); return NextResponse.json({ message: "Success" }, { status: 200 }); } catch (error) { return NextResponse.json({ message: "Error setting cookie" }, { status: 500 }); } }
-
Logout Route (
logout/route.ts
)- This endpoint is supposed to clear the JWT token from cookies.
import { NextResponse, NextRequest } from "next/server"; export function POST(request: NextRequest) { try { const response = NextResponse.json({ message: "Success" }, { status: 200 }); response.cookies.set('token', '', { expires: new Date(0), path: '/' }); // Ensure matching path for deletion return response; } catch (error) { return NextResponse.json({ message: "Failed to log out" }, { status: 400 }); } }
-
Middleware (
middleware.ts
)- This file is responsible for verifying the JWT token for protected routes and redirecting accordingly.
import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; import { jwtVerify } from 'jose'; export async function middleware(request: NextRequest) { const { pathname } = request.nextUrl; const token = request.cookies.get("token")?.value; const secret = new TextEncoder().encode("secret"); if (pathname.startsWith("/api/logout")) { // Clear the token cookie const res = NextResponse.next(); res.cookies.set("token", "", { expires: new Date(0) }); } // Check paths requiring logged-in user if (pathname.startsWith("/cart") || pathname.startsWith("/checkout") || pathname.startsWith("/track") || pathname.startsWith("/profile")) { if (!token) return NextResponse.redirect(new URL('/login', request.url)); try { const { payload } = await jwtVerify(token, secret); if (!payload || (payload.user_type !== 1 && pathname !== "/profile")) { return NextResponse.redirect(new URL('/login', request.url)); } return NextResponse.next(); } catch (error) { return NextResponse.redirect(new URL('/login', request.url)); } // Check paths for vendor/admin access } else if (pathname.startsWith("/vendor/") || pathname.startsWith("/vendorincoming") || pathname.startsWith("/admin")) { if (!token) return NextResponse.redirect(new URL('/', request.url)); try { const { payload } = await jwtVerify(token, secret); if (!payload || pathname.replace("/admin/", "") !== payload.id || payload.user_type !== 2) { return NextResponse.redirect(new URL('/', request.url)); } return NextResponse.next(); } catch (error) { return NextResponse.redirect(new URL('/login', request.url)); } // Redirect logged-in users from login/signup pages } else if (pathname.startsWith("/login") || pathname.startsWith("/signup") || pathname.startsWith("/vendorob")) { if (token) return NextResponse.redirect(new URL('/', request.url)); } return NextResponse.next(); } // Middleware configuration for matching routes export const config = { matcher: [ "/", "/cart", "/checkout", "/login", "/signup", "/track/:path*", "/menu/:path*", "/profile/:path*", "/admin/:path*", "/vendor/:path*", "/vendorincoming/:path*", "/vendorob", ], };
Issue
Despite setting expires: new Date(0)
to clear the cookie in logout/route.ts
and attempting to handle it in middleware, I sometimes notice:
- The cookie doesn’t seem to be cleared immediately, leading to inconsistent behavior.
- Protected routes don’t always redirect correctly after logging out, which suggests that the cookie might still exist momentarily.
What I’ve Tried
- Verified the
path
in the cookie deletion matches the one used during setting. - Used both
set
with an empty value andexpires: new Date(0)
to delete the cookie. - Adjusted middleware to handle token verification errors and clear cookies conditionally.
- Tested on both localhost and deployed environments to check for domain-specific behavior.
Question
What am I missing to ensure the cookie is consistently deleted upon logout? Could there be an issue with my middleware.ts
handling or something specific to Next.js cookies? I have gone through different questions but they just point out how they the poster is deleting the request side cookie and not the response side cookie but I have considered that.
Any guidance on best practices for managing cookies in Next.js with middleware
would be highly appreciated.