Поток файлов cookie следующий:
Код: Выделить всё
user clicks login with discord
redirected to discord's oauth page
discord sends the data to my backend api
user account is created/updated on my database
redirects back to my authCallback page
sends another request to users/sessions to store the user's data in cookies
/sessions route sets the cookies
единственный маршрут, который вызывает функцию cookie, — это пользователи/сессии
Вот необходимый код:
у меня есть 2 vercel.json, 1 для внешнего интерфейса и один для внутреннего интерфейса (на случай, если кому-то понадобится знать)
vercel.json (интерфейс):
Код: Выделить всё
{
"rewrites":
[
{
"source": "/(.*)",
"destination": "/"
}
]
}
Код: Выделить всё
{
"builds": [
{
"src": "/index.js",
"use": "@vercel/node"
}
],
"routes": [
{
"src": "/(.*)",
"dest": "/index.js",
"methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]
}
]
}
Код: Выделить всё
const allowedOrigins = [
// Front End
"http://localhost:5173",
prodDomain,
// Back End
"http://localhost:4000",
prodDomainAPI,
];
app.set("trust proxy", 1);
app.use(
cors({
credentials: true,
origin: (origin, callback) => {
if (allowedOrigins.indexOf(origin) !== -1 || !origin) {
callback(null, true);
} else {
callback(new Error("Not allowed by CORS"));
}
},
})
);
app.use(cookieParser());
app.use(json());
app.use(urlencoded({ extended: true }));
Код: Выделить всё
export const setAuthCookies = (res, userData, token) => {
const commonOptions = {
path: "/",
sameSite: "None",
secure: true,
maxAge: 30 * 24 * 60 * 60 * 1000,
};
res.cookie("authUser", JSON.stringify(userData), {
...commonOptions,
httpOnly: false,
});
res.cookie("authToken", token, {
...commonOptions,
httpOnly: true,
});
return res;
};
export const clearAuthCookies = (res) => {
const clearOptions = {
path: "/",
sameSite: "None",
secure: true,
httpOnly: true,
};
res.clearCookie("authToken", clearOptions);
return res;
};
Код: Выделить всё
users.post("/sessions", requireAuth(), async (req, res) => {
const decodedUserData = req.user.decodedUser;
try {
const existingUser = await getUserByID(decodedUserData.id);
if (!existingUser) {
return res.status(404).send("User not found");
}
const userRole = await getUserRole(decodedUserData.id);
const userData = {
id: existingUser.id,
discord_id: existingUser.discord_id,
username: existingUser.username,
avatar_url: existingUser.avatar_url,
banner_color: existingUser.banner_color,
banner_url: existingUser.banner_url,
role: userRole,
created_at: existingUser.created_at,
};
setAuthCookies(res, userData, req.user.token);
res.status(200).json({
success: true,
message: "Session established successfully",
});
} catch (error) {
console.error("users.POST /sessions", { error });
res.status(500).send("Internal Server Error");
}
});
AuthCallback.jsx (интерфейс)
Код: Выделить всё
export const AuthCallback = () => {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
useEffect(() => {
const run = async () => {
const reason = searchParams.get("reason");
const token = searchParams.get("token");
if (reason) {
let errorMessage = null;
switch (reason) {
case "rate_limited":
errorMessage =
"Discord is rate limiting login attempts. Please wait a few minutes and try again.";
break;
case "auth_failed":
errorMessage = "Authentication failed. Please try again.";
break;
default:
errorMessage = "An error occurred during authentication.";
}
toast.error(errorMessage, {
containerId: "notify-failure",
});
return setTimeout(() => {
navigate("/");
}, 1000);
}
try {
// No token means backend error
if (!token) {
toast.error("Authentication failed. Please try again.", {
containerId: "notify-failure",
});
return setTimeout(() => {
navigate("/");
}, 1000);
}
await axios
.post(
`${API}/users/sessions`,
{},
{
headers: {
Authorization: `Bearer ${token}`,
},
withCredentials: true,
},
)
.then((res) => {
toast.success("Successfully logged in!", {
containerId: "notify-success",
});
})
.catch((err) => {
toast.error("Failed to load user data. Please try again.", {
containerId: "notify-failure",
});
});
} catch (err) {
console.error("OAuth callback error:", err);
toast.error("Failed to load user data. Please try again.", {
containerId: "notify-failure",
});
} finally {
// Only reload if we have a token (success case)
if (token) {
console.log("token received");
setTimeout(() => window.location.reload(), 1000);
}
}
};
run();
}, [navigate, searchParams]);
return (
Authenticating...
Please wait
);
};
Код: Выделить всё
Adding domain to the commonOptions
const commonOptions = {
path: "/",
sameSite: "None",
secure: true,
maxAge: 30 * 24 * 60 * 60 * 1000,
domain: isProd ? prodDomain : "localhost"
};
using sameSite Lax, Strict and None, as well as using lowercase for all 3 (lax, strict, none)
console logging the headers which showed:
'set-cookie': [
'authUser='
'authToken='
]
Я также заметил, что при производстве прямо перед обновлением страницы в файлах cookie есть 4 файла cookie, по 2 набора каждого (authUser, authToken). Один из них - мой фактический домен, а другой - мой домен с точкой перед это. Я предполагаю, что браузер делает это автоматически, поскольку я не настраиваю домен.
на локальном хосте все это работает нормально, он устанавливает файлы cookie, и я могу нормально войти в систему.
на производстве он устанавливает файлы cookie, но как только страница обновляет оба файла cookie, набор серверных файлов автоматически удаляется.
Я могу получить данные из /sessions и просто настроить внешний интерфейс напрямую с помощью js-cookies, но я думаю это было бы менее безопасно, поскольку нельзя установить httpOnly
Подробнее здесь: https://stackoverflow.com/questions/798 ... ress-react
Мобильная версия