6.1 Cookie Session 存储
Remix 提供 createCookieSessionStorage 工具函数,将 Session 数据加密存储在 Cookie 中(无需数据库)。这是最简单的 Session 存储方式,适合大多数应用场景。
// app/session.server.ts — Session 配置模块
import { createCookieSessionStorage, redirect } from "@remix-run/node";
// 创建 Session 存储
const { getSession, commitSession, destroySession } =
createCookieSessionStorage({
cookie: {
name: "__session",
httpOnly: true, // 防 XSS:JS 无法读取
maxAge: 60 * 60 * 24 * 30, // 30 天(秒)
path: "/",
sameSite: "lax", // 防 CSRF
secrets: [process.env.SESSION_SECRET!], // 签名密钥
secure: process.env.NODE_ENV === "production", // HTTPS only
},
});
export { getSession, commitSession, destroySession };
// 从 Request 获取当前用户(路由守卫辅助函数)
export async function getUserId(request: Request) {
const session = await getSession(request.headers.get("Cookie"));
return session.get("userId") as string | undefined;
}
// 强制要求登录(未登录则重定向到 /login)
export async function requireUser(request: Request) {
const userId = await getUserId(request);
if (!userId) {
const url = new URL(request.url);
throw redirect(`/login?redirectTo=${url.pathname}`);
}
const user = await getUserById(userId);
if (!user) throw redirect("/login");
return user;
}
TS
6.2 登录 Action
登录流程:验证凭证 → 写入 Session → 重定向到目标页面。
// app/routes/login.tsx
import { json, redirect, type ActionFunctionArgs } from "@remix-run/node";
import { Form, useActionData } from "@remix-run/react";
import { getSession, commitSession } from "~/session.server";
import { verifyLogin } from "~/models/user.server";
export async function action({ request }: ActionFunctionArgs) {
const form = await request.formData();
const email = form.get("email") as string;
const password = form.get("password") as string;
const redirectTo = form.get("redirectTo") as string || "/";
// 验证凭证(bcrypt 比对)
const user = await verifyLogin(email, password);
if (!user) {
return json({ error: "邮箱或密码错误" }, { status: 401 });
}
// 创建 Session,写入 userId
const session = await getSession(request.headers.get("Cookie"));
session.set("userId", user.id);
// 重定向并在响应头中设置 Cookie
return redirect(redirectTo, {
headers: {
"Set-Cookie": await commitSession(session),
},
});
}
export default function LoginPage() {
const actionData = useActionData<typeof action>();
return (
<Form method="post">
<input type="hidden" name="redirectTo"
value={new URLSearchParams(location.search).get("redirectTo") ?? "/"} />
<input name="email" type="email" placeholder="邮箱" />
<input name="password" type="password" placeholder="密码" />
{actionData?.error && <p style={{ color: "red" }}>{actionData.error}</p>}
<button type="submit">登录</button>
</Form>
);
}
TSX
6.3 退出登录
// app/routes/logout.tsx
import { redirect, type ActionFunctionArgs } from "@remix-run/node";
import { Form } from "@remix-run/react";
import { getSession, destroySession } from "~/session.server";
export async function action({ request }: ActionFunctionArgs) {
const session = await getSession(request.headers.get("Cookie"));
return redirect("/login", {
headers: { "Set-Cookie": await destroySession(session) },
});
}
// 退出按钮(POST 提交,防 CSRF)
export function LogoutButton() {
return (
<Form action="/logout" method="post">
<button type="submit">退出登录</button>
</Form>
);
}
TSX
6.4 Flash Message — 一次性消息
Flash Message 是只读取一次的 Session 数据,用于在重定向后显示成功/错误通知(如"文章已发布")。Remix 内建支持 session.flash()。
// 在 action 中写入 flash 消息
const session = await getSession(request.headers.get("Cookie"));
session.flash("success", "文章已成功发布!");
return redirect("/blog", {
headers: { "Set-Cookie": await commitSession(session) },
});
// 在 loader 中读取并清除 flash 消息
export async function loader({ request }: LoaderFunctionArgs) {
const session = await getSession(request.headers.get("Cookie"));
const success = session.get("success"); // 读取后自动从 session 删除
return json({ success }, {
headers: { "Set-Cookie": await commitSession(session) }, // 必须提交以清除
});
}
TSX
6.5 remix-auth — OAuth2 第三方登录
remix-auth 是 Remix 生态最流行的认证库,提供 Strategy 模式支持 GitHub、Google、Discord 等 OAuth2 提供商。
npm install remix-auth remix-auth-github
SHELL
// app/auth.server.ts
import { Authenticator } from "remix-auth";
import { GitHubStrategy } from "remix-auth-github";
import { sessionStorage } from "~/session.server";
export const authenticator = new Authenticator<User>(sessionStorage);
authenticator.use(
new GitHubStrategy(
{
clientID: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
callbackURL: "http://localhost:5173/auth/github/callback",
},
async ({ profile }) => {
// 查找或创建用户
return findOrCreateUser({
githubId: profile.id,
name: profile.displayName,
email: profile.emails?.[0].value,
});
}
)
);
TS
// app/routes/auth.github.tsx — 触发 OAuth 跳转
export async function loader({ request }: LoaderFunctionArgs) {
return authenticator.authenticate("github", request);
}
// app/routes/auth.github.callback.tsx — OAuth 回调
export async function loader({ request }: LoaderFunctionArgs) {
return authenticator.authenticate("github", request, {
successRedirect: "/",
failureRedirect: "/login",
});
}
TSX
本章小结Remix 的认证基于 Web 标准 Cookie API:用 createCookieSessionStorage 创建会话存储;登录 Action 验证凭证后写入 Session 并 redirect;requireUser 函数在 Loader 中做路由守卫;session.flash() 实现一次性通知消息;remix-auth 提供开箱即用的 OAuth2 集成。这套方案无需额外数据库,安全可靠。