Next.js获取用户Cookie异常求助:已登录却无法读取JWT Cookie
问题描述
自定义登录API,登录成功后将JWT存入用户Cookie,期望实现登录后不再渲染注册和登录链接,但当前代码失效:即便Cookie中存在JWT,console.log输出Cookie时仍显示undefined。
Header组件代码
'use client'; import Link from 'next/link'; import { useState, useEffect } from 'react'; export default function Header() { const [isLoggedIn, setIsLoggedIn] = useState(false); useEffect(() => { console.log("useEffect is running..."); if (typeof window !== 'undefined') { const cookies = document.cookie; console.log("All cookies:", cookies); const token = cookies.split(';').find(cookie => cookie.trim().startsWith('token=')); console.log("Token from cookies:", token); setIsLoggedIn(!!token); } }, []); return ( <header className='bg-white border-b flex justify-between p-4'> <div className="flex items-center gap-6"> <Link href={'/'}>FashionFinder </Link> <nav className='flex gap-4'> <Link href={'/about'}>About</Link> <Link href={'/contact'}>Contact</Link> <Link href={'/albums'}>Albums</Link> </nav> </div> <nav className='flex items-center gap-4'> {isLoggedIn ? ( <Link href={'/logout'}>Sign Out</Link> ) : ( <> <Link href={'/login'}>Sign In</Link> <Link href={'/register'}>Create Account</Link> </> )} </nav> </header> ) }
Login路由代码(route.js)
import { connect } from "@/dbConfig/dbConfig"; import User from "@/models/userModel"; import { NextRequest, NextResponse } from "next/server"; import bcryptjs from "bcryptjs"; import jwt from "jsonwebtoken"; connect() export async function POST(request) { try{ const reqBody= await request.json() const {email, password} = reqBody; console.log(reqBody) const lowercasedEmail = email.toLowerCase(); //check if the user exists const user = await User.findOne({email: lowercasedEmail}) if (!user){ return NextResponse.json({error:"User does not exist"}, {status: 400}) } //check if valid email format const isValidEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); if (!isValidEmail) { return NextResponse.json({ error: "Invalid email format" }, { status: 300 }); } //check if the password is correct const validPassword = await bcryptjs.compare(password, user.password) if (!validPassword){ return NextResponse.json({error: "Invalid password"}, {status:500}) } //create token data const tokenData = { id: user._id, email: user.email } //create token const token = await jwt.sign(tokenData, process.env.TOKEN_SECRET, {expiresIn: "1d"}) const response = NextResponse.json({ message: "Login successful", success: true }) response.cookies.set("token", token, { httpOnly: true }) return response; } catch (error){ return NextResponse.json({error: error.message}, {status: 500}) } }
解决思路
1. 核心问题:HttpOnly Cookie无法被前端读取
你设置了httpOnly: true的Cookie,前端JavaScript无法通过document.cookie访问这类Cookie,这就是console.log输出undefined的根本原因。HttpOnly是安全属性,不建议直接移除,推荐通过服务器端验证Cookie状态。
2. 解决方案:服务器端校验登录状态
创建一个后端API用于校验登录状态,前端通过调用该API获取登录状态:
步骤1:创建校验API(app/api/auth/me/route.js)
import { connect } from "@/dbConfig/dbConfig"; import jwt from "jsonwebtoken"; import { NextRequest, NextResponse } from "next/server"; connect(); export async function GET(request) { try { // 服务器端读取HttpOnly Cookie const token = request.cookies.get("token")?.value; if (!token) { return NextResponse.json({ isLoggedIn: false }, { status: 200 }); } // 验证JWT有效性 jwt.verify(token, process.env.TOKEN_SECRET); return NextResponse.json({ isLoggedIn: true }, { status: 200 }); } catch (error) { // 验证失败或其他错误,返回未登录状态 return NextResponse.json({ isLoggedIn: false }, { status: 200 }); } }
步骤2:修改Header组件调用校验API
'use client'; import Link from 'next/link'; import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; export default function Header() { const [isLoggedIn, setIsLoggedIn] = useState(false); const router = useRouter(); // 校验登录状态的函数 const checkLoginStatus = async () => { try { const res = await fetch('/api/auth/me', { credentials: 'include' }); const data = await res.json(); setIsLoggedIn(data.isLoggedIn); } catch (error) { setIsLoggedIn(false); } }; useEffect(() => { checkLoginStatus(); // 监听路由变化,每次页面切换重新校验状态 const handleRouteChange = () => checkLoginStatus(); router.events.on('routeChangeComplete', handleRouteChange); // 组件卸载时移除监听 return () => router.events.off('routeChangeComplete', handleRouteChange); }, [router]); return ( <header className='bg-white border-b flex justify-between p-4'> <div className="flex items-center gap-6"> <Link href={'/'}>FashionFinder </Link> <nav className='flex gap-4'> <Link href={'/about'}>About</Link> <Link href={'/contact'}>Contact</Link> <Link href={'/albums'}>Albums</Link> </nav> </div> <nav className='flex items-center gap-4'> {isLoggedIn ? ( <Link href={'/logout'}>Sign Out</Link> ) : ( <> <Link href={'/login'}>Sign In</Link> <Link href={'/register'}>Create Account</Link> </> )} </nav> </header> ) }
3. 优化Cookie设置
在Login路由中完善Cookie的安全属性,避免跨域或路径问题:
response.cookies.set("token", token, { httpOnly: true, path: "/", // 确保全站可访问 sameSite: "lax", // 防止CSRF secure: process.env.NODE_ENV === "production" // 生产环境开启HTTPS传输 })
4. 登录成功后触发路由跳转
在登录页面调用API成功后,执行路由跳转(比如跳转到首页),触发Header组件的状态重新校验:
const handleLogin = async () => { const res = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); const data = await res.json(); if (data.success) { router.push('/'); // 跳转触发Header状态更新 } };
内容的提问来源于stack exchange,提问作者gj_dev
相关产品推荐
相关产品推荐

