我正在开发一个从 React 前端到 ExpressJs 后端进行 API 调用的应用程序,一切在本地开发中都运行良好,但在生产中却不起作用。
我在 Heroku 上托管后端,在 Netlify 上托管前端。
我可以登录,但无法查看我的个人资料,当我尝试查看我的个人资料时,它说令牌/cookie 未定义。
这是我的后台登录
const loginUser = async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { email, password } = req.body;
try {
// Check if the user exists
const user = await User.findOne({ email });
if (!user) {
return res.status(400).json({ msg: "Invalid credentials" });
}
// Check if the password matches
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(400).json({ msg: "Invalid credentials" });
}
// Create a payload with user ID and role (if applicable)
const payload = {
user: {
id: user.id,
role: user.role,
},
};
// Sign a JWT token
jwt.sign(
payload,
process.env.JWT_SECRET,
{ expiresIn: "2h" }, // Token valid for 2 hours
(err, token) => {
if (err) throw err;
// Set the token as an HTTP-only cookie
res.cookie("token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production", // Use secure cookies in production
sameSite: "strict", // Prevent CSRF attacks
maxAge: 2 * 60 * 60 * 1000, // 2 hours in milliseconds
});
// Respond with user data and token
res.status(200).json({
user: {
id: user.id,
email: user.email,
first_name: user.first_name,
last_name: user.last_name,
role: user.role,
},
token: token, // Include the token in the response body
});
}
);
} catch (err) {
console.error(err.message);
res.status(500).json({ msg: "Server error" });
}
};
以及路线
router.post(
"/api/login",
[
check("email", "Please include a valid email").isEmail(),
check("password", "Password is required").exists(),
],
userController.loginUser
);
前端登录
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import axios from "axios";
const UserLoginForm = ({ setUser }) => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const navigate = useNavigate();
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await axios.post(
"https://app.herokuapp.com/users/api/login",
{ email, password },
{ withCredentials: true }
);
setUser(response.data.user);
console.log(response.data);
localStorage.setItem("user", JSON.stringify(response.data.user));
navigate("/");
} catch (err) {
setError(err.response?.data?.message || "An error occurred during login");
}
};
return (
<div className="min-h-screen bg-gray-100 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
Sign in to your account
</h2>
</div>
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<form className="space-y-6" onSubmit={handleSubmit}>
<div>
<label
htmlFor="email"
className="block text-sm font-medium text-gray-700"
>
Email address
</label>
<div className="mt-1">
<input
id="email"
name="email"
type="email"
autoComplete="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
/>
</div>
</div>
<div>
<label
htmlFor="password"
className="block text-sm font-medium text-gray-700"
>
Password
</label>
<div className="mt-1">
<input
id="password"
name="password"
type="password"
autoComplete="current-password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
/>
</div>
</div>
<div>
<button
type="submit"
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Sign in
</button>
</div>
</form>
{error && (
<div className="mt-4 text-center text-red-600">{error}</div>
)}
</div>
</div>
</div>
);
};
export default UserLoginForm;
中间件
const jwt = require("jsonwebtoken");
const User = require("../models/User");
const Salon = require("../models/Salon");
const authMiddleware = async (req, res, next) => {
console.log(req.cookies, "this is cookies");
try {
// Get token from cookie
const token = req.cookies.token || req.header("x-auth-token");
// Check if no token
if (!token) {
req.user = null;
req.salon = null;
res.locals.authenticatedEntityId = null;
return next();
}
// Verify token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
if (decoded.user) {
// If the token contains user information
const user = await User.findById(decoded.user.id).select("-password");
if (!user) {
throw new Error("User not found");
}
req.user = user;
req.salon = null;
res.locals.authenticatedEntityId = user._id;
} else if (decoded.salon) {
// If the token contains salon information
const salon = await Salon.findById(decoded.salon.id).select("-password");
if (!salon) {
throw new Error("Salon not found");
}
req.salon = salon;
req.user = null;
res.locals.authenticatedEntityId = salon._id;
} else {
// If the token doesn't contain user or salon information
req.user = null;
req.salon = null;
res.locals.authenticatedEntityId = null;
}
next();
} catch (error) {
console.error("Auth Middleware Error:", error);
req.user = null;
req.salon = null;
res.locals.authenticatedEntityId = null;
next();
}
};
module.exports = authMiddleware;
中间件返回未定义的令牌
当我登录时,它会在 cookie 中显示令牌,但如果我单击任何链接或刷新页面,令牌就会消失,因此我无法查看我的个人资料或执行任何需要我使用用户的操作。
请记住,它非常适合本地开发,但不适用于生产。
我的 Api 托管在 Heroku 上,前端托管在 netlify 上
当您的前端和后端托管在不同的域上时(例如前端为 Netlify,后端为 Heroku),您可能会因 cookie 中的 sameSite 属性而遇到跨源请求问题。如果您设置 SameSite:“strict”,浏览器可能会阻止 Cookie 在这些域之间发送,这可能会干扰身份验证或会话持久性。
要解决此问题,您应该使用 sameSite: "none" 而不是 "strict"。但是,当使用 sameSite: "none" 时,cookie 还必须标记为安全,这意味着它只会通过 HTTPS 连接发送。