MERN栈部署Vercel后JWT Cookie刷新丢失问题求助
MERN栈部署Vercel后刷新丢失登录状态问题解决
问题概述
将MERN栈网站部署至Vercel(前端与API均部署)后,登录成功页面右上角显示“Welcome User”,但刷新页面会回到登录/注册状态。本地localhost:3000环境完全正常,刷新时JWT token可正常存储在Cookie中,仅部署到Vercel后出现该问题。
相关代码配置
后端导入模块配置
const express = require('express'); const app = express(); const path = require('path') const mongoose = require("mongoose") const cors = require('cors') const bcrypt = require('bcrypt') const jwt = require('jsonwebtoken') const UserModel = require('./models/User') const CookieParser = require('cookie-parser') const multer = require('multer') const dotenv = require('dotenv') const PostModel = require("./models/Post.js") // app.use(cors({credentials : true , origin : 'https://blogifyv1.vercel.app'})) app.use(cors({credentials : true , origin : true})) app.use(express.json()) app.use(CookieParser()) app.use('/uploads',express.static(__dirname + '/uploads')) // 用于从uploads文件夹展示图片 app.use(express.urlencoded({ extended : false }))
登录路由
添加domain前的代码
app.post('/login',async(req,res)=>{ const {username , password} = req.body; try { const userDoc = await UserModel.findOne({username : username}) const passwordMatched = await bcrypt.compareSync(password , userDoc.password) console.log("isValid",passwordMatched) // 生成token if(passwordMatched){ jwt.sign({username , id:userDoc._id}, secretSalt,{},(err,token)=>{ if(err){ throw err; } else{ res.cookie('token',token).json({ id : userDoc._id, username }) } }) } else{ res.status(400).json("Wrong Credentials") } } catch (err) { console.log(err) res.status(400).json(err) } })
添加domain后的代码
app.post('/login',async(req,res)=>{ const {username , password} = req.body; try { const userDoc = await UserModel.findOne({username : username}) const passwordMatched = await bcrypt.compareSync(password , userDoc.password) console.log("isValid",passwordMatched) // 生成token if(passwordMatched){ jwt.sign({username , id:userDoc._id}, secretSalt,{},(err,token)=>{ if(err){ throw err; } else{ res.cookie('token',token,{ domain: '.vercel.app', secure: true }).json({ id : userDoc._id, username }) } }) } else{ res.status(400).json("Wrong Credentials") } } catch (err) { console.log(err) res.status(400).json(err) } })
创建文章路由及登出接口
// 登出接口 app.post('/logout',async(req,res)=>{ res.cookie('token' , "").json("ok") }) // 创建文章接口 app.post('/post',uploadMiddleware.single('file'),async(req,res)=>{// 'file'对应CreatePost.js中data.set('file',file)的键 res.json({files : req.file}) console.log("file", req.file) console.log("filename", req.file?.filename) const {token} = req.cookies jwt.verify(token,secretSalt,{},async (err,info)=>{ if(err) throw err; const {title , summary,content} = req.body; const postDoc = await PostModel.create({ title, summary, content, cover : req.file.filename, author : info.id, }) res.json(postDoc) }) })
React登录组件(Login.js)
import React, { useContext, useState } from 'react' import Navbar from '../components/Navbar' import Form from '../components/Form' import { useNavigate } from 'react-router'; import { UserContext } from '../contextapi/UserContext'; const Login = () => { const [username , setUsername] = useState(''); const [password , setPassword] = useState(''); const navigate = useNavigate(); const {setUserInfo} = useContext(UserContext); const login=async(e)=>{ e.preventDefault(); try { const response = await fetch('https://mern-blog-api-rho.vercel.app/login',{ method: "POST", body : JSON.stringify({username , password}), headers: {'Content-type' : 'application/json'}, credentials: "include", // 允许携带Cookie }) console.log(response) if(response.status === 200){ response.json().then((userInfo)=>{ setUserInfo(userInfo) navigate('/') }) } else{ alert('Registration Failed , try again later') } } catch (error) { console.log(error) } }
Vercel.json配置
{ "version":2, "builds":[ {"src":"./index.js","use":"@vercel/node"} ], "routes": [ { "src": "/(.*)", "dest": "/", "methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"], "headers": { "Access-Control-Allow-Origin": "*" } } ] }
修复方案
1. 修正CORS配置
将后端CORS配置改为明确指定前端域名,避免通配符与credentials: true冲突:
app.use(cors({ credentials: true, origin: 'https://你的前端Vercel域名' // 例如https://blogifyv1.vercel.app }))
同时删除vercel.json中Access-Control-Allow-Origin: "*"的配置,由CORS中间件统一处理跨域头。
2. 调整Cookie属性设置
删除domain: '.vercel.app',补充sameSite和httpOnly属性,适配跨域场景:
// 登录接口的Cookie设置 res.cookie('token', token, { secure: true, // HTTPS环境必须开启 sameSite: 'none', // 跨域场景必须设置为none httpOnly: true, // 可选,提升安全性,防止XSS maxAge: 24 * 60 * 60 * 1000 // 可选,设置Cookie有效期,比如1天 }).json({ id: userDoc._id, username }) // 登出接口的Cookie设置 app.post('/logout',async(req,res)=>{ res.cookie('token', "", { secure: true, sameSite: 'none', httpOnly: true, expires: new Date(0) // 立即过期 }).json("ok") })
3. 前端添加页面刷新时的身份校验
在前端根组件(如App.js)中添加初始化校验,刷新页面时自动验证Cookie中的token:
后端新增校验接口
app.get('/profile', (req, res) => { const { token } = req.cookies; if (!token) return res.status(401).json("未登录"); jwt.verify(token, secretSalt, {}, (err, info) => { if (err) return res.status(401).json("token无效"); res.json(info); }); })
前端根组件添加校验逻辑
import { useContext, useEffect } from 'react'; import { UserContext } from './contextapi/UserContext'; function App() { const { setUserInfo } = useContext(UserContext); useEffect(() => { // 页面加载时校验登录状态 fetch('https://mern-blog-api-rho.vercel.app/profile', { credentials: "include" }).then(res => { if (res.status === 200) { res.json().then(userInfo => setUserInfo(userInfo)); } }).catch(err => console.error(err)); }, []); // ...其他组件渲染逻辑 } export default App;
4. 更新Vercel配置
修改vercel.json,移除冲突的跨域头配置:
{ "version":2, "builds":[ {"src":"./index.js","use":"@vercel/node"} ], "routes": [ { "src": "/(.*)", "dest": "/", "methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] } ] }
内容的提问来源于stack exchange,提问作者Yash Kamble
相关产品推荐
相关产品推荐

