跳转到内容

Cookies

cookie 是您的服务器在 HTTP 响应中发送给某人的一小段信息,其浏览器将在后续请求中发回。此技术是许多交互式网站的基本构建块,可添加状态,以便您可以构建身份验证(请参阅 sessions)、购物车、用户偏好设置以及许多其他需要记住谁已登录的功能。

Remix 的 Cookie 接口为 cookie 元数据提供了一个逻辑的、可重复使用的容器。

使用 cookies

虽然您可以手动创建这些 cookie,但更常见的是使用 会话存储

在 Remix 中,您通常会在 loader 和 / 或 action 函数中使用 cookie(请参阅 mutations),因为这些是您需要读取和写入数据的地方。

假设您的电子商务网站上有一个横幅,提示用户查看您目前正在销售的商品。该横幅横跨您的主页顶部,并在侧面包含一个按钮,允许用户关闭横幅,以便他们至少在一周内不会看到它。

首先,创建一个 cookie:

app/cookies.server.ts
import { createCookie } from "@remix-run/node"; // or cloudflare/deno
export const userPrefs = createCookie("user-prefs", {
maxAge: 604_800, // one week
});

然后,您可以 import cookie 并在 loader 和 / 或 action 中使用它。在这种情况下,loader 仅检查用户偏好的值,因此您可以在组件中使用它来决定是否呈现横幅。单击按钮时,<form> 会调用服务器上的 action 并重新加载没有横幅的页面。

注意: 我们(目前)建议您在 *.server.ts 文件中创建应用所需的所有 cookie,并将它们 import 到路由模块中。这样,Remix 编译器就可以正确地从不需要它们的浏览器构建中删去这些导入。我们希望最终消除这一警告。

app/routes/_index.tsx
import type {
ActionFunctionArgs,
LoaderFunctionArgs,
} from "@remix-run/node"; // or cloudflare/deno
import { json, redirect } from "@remix-run/node"; // or cloudflare/deno
import {
useLoaderData,
Link,
Form,
} from "@remix-run/react";
import { userPrefs } from "~/cookies.server";
export async function loader({
request,
}: LoaderFunctionArgs) {
const cookieHeader = request.headers.get("Cookie");
const cookie =
(await userPrefs.parse(cookieHeader)) || {};
return json({ showBanner: cookie.showBanner });
}
export async function action({
request,
}: ActionFunctionArgs) {
const cookieHeader = request.headers.get("Cookie");
const cookie =
(await userPrefs.parse(cookieHeader)) || {};
const bodyParams = await request.formData();
if (bodyParams.get("bannerVisibility") === "hidden") {
cookie.showBanner = false;
}
return redirect("/", {
headers: {
"Set-Cookie": await userPrefs.serialize(cookie),
},
});
}
export default function Home() {
const { showBanner } = useLoaderData<typeof loader>();
return (
<div>
{showBanner ? (
<div>
<Link to="/sale">Don't miss our sale!</Link>
<Form method="post">
<input
type="hidden"
name="bannerVisibility"
value="hidden"
/>
<button type="submit">Hide</button>
</Form>
</div>
) : null}
<h1>Welcome!</h1>
</div>
);
}

Cookie 具有 多个属性,用于控制其何时过期、如何访问以及发送到何处。这些属性中的任何一个都可以在 createCookie(name, options) 中指定,也可以在生成 Set-Cookie 标头时在 serialize() 期间指定。

const cookie = createCookie("user-prefs", {
// These are defaults for this cookie.
path: "/",
sameSite: "lax",
httpOnly: true,
secure: true,
expires: new Date(Date.now() + 60_000),
maxAge: 60,
});
// You can either use the defaults:
cookie.serialize(userPrefs);
// Or override individual ones as needed:
cookie.serialize(userPrefs, { sameSite: "strict" });

请阅读有关这些属性的更多信息以更好地了解它们的作用。

cookies 签名

可以对 cookie 进行签名,以便在收到 cookie 时自动验证其内容。由于伪造 HTTP 标头相对容易,因此对于您不希望有人伪造的任何信息(如身份验证信息),这都是一个好主意(请参阅 sessions)。

要签署 Cookie,请在首次创建 Cookie 时提供一个或多个秘密

const cookie = createCookie("user-prefs", {
secrets: ["s3cret1"],
});

具有一个或多个秘密的 Cookie 将以确保 Cookie 完整性的方式进行存储和验证。

可以通过将新密钥添加到 secrets 数组的前面来轮换密钥。使用旧密钥签名的 Cookie 仍将在 cookie.parse() 中成功解码,并且最新密钥(数组中的第一个密钥)将始终用于签署在 cookie.serialize() 中创建的传出 Cookie。

app/cookies.server.ts
export const cookie = createCookie("user-prefs", {
secrets: ["n3wsecr3t", "olds3cret"],
});
app/routes/route.tsx
import { cookie } from "~/cookies.server";
export async function loader({
request,
}: LoaderFunctionArgs) {
const oldCookie = request.headers.get("Cookie");
// oldCookie may have been signed with "olds3cret", but still parses ok
const value = await cookie.parse(oldCookie);
new Response("...", {
headers: {
// Set-Cookie is signed with "n3wsecr3t"
"Set-Cookie": await cookie.serialize(value),
},
});
}

createCookie

创建一个逻辑容器来管理来自服务器的浏览器 cookie。

import { createCookie } from "@remix-run/node"; // or cloudflare/deno
const cookie = createCookie("cookie-name", {
// all of these are optional defaults that can be overridden at runtime
expires: new Date(Date.now() + 60_000),
httpOnly: true,
maxAge: 60,
path: "/",
sameSite: "lax",
secrets: ["s3cret1"],
secure: true,
});

要了解有关每个属性的更多信息,请参阅 MDN Set-Cookie 文档

isCookie

如果对象是 Remix cookie 容器,则返回 true

import { isCookie } from "@remix-run/node"; // or cloudflare/deno
const cookie = createCookie("user-prefs");
console.log(isCookie(cookie));
// true

createCookie 返回一个 cookie 容器,它具有一些属性和方法。

const cookie = createCookie(name);
cookie.name;
cookie.parse();
// etc.

cookie.name

Cookie 的名称,用于 CookieSet-Cookie HTTP 标头。

cookie.parse() 复制代码

在给定的 Cookie 标头中提取并返回此 cookie 的值。

const value = await cookie.parse(
request.headers.get("Cookie")
);

cookie.serialize()

序列化一个值并将其与此 cookie 的选项相结合以创建一个 Set-Cookie 标头,适合在传出的 Response 中使用。

new Response("...", {
headers: {
"Set-Cookie": await cookie.serialize({
showBanner: true,
}),
},
});

cookie.isSigned

如果 cookie 使用任何秘密,则为 true,否则为 false

let cookie = createCookie("user-prefs");
console.log(cookie.isSigned); // false
cookie = createCookie("user-prefs", {
secrets: ["soopersekrit"],
});
console.log(cookie.isSigned); // true

cookie.expires

此 Cookie 的过期日期。请注意,如果 Cookie 同时具有 maxAgeexpires,则此值将是当前日期加上 maxAge 值,因为 Max-Age 优先于 Expires

const cookie = createCookie("user-prefs", {
expires: new Date("2021-01-01"),
});
console.log(cookie.expires); // "2020-01-01T00:00:00.000Z"