Fresh 身份验证设置方法
身份验证和会话管理是构建现代 Web 应用程序最基本方面之一。这对于隐藏高级内容或创建仅限管理员访问的部分(如仪表板)是必要的。
Fresh 是一个边缘原生 Web 框架,它通过服务器端渲染和岛屿架构拥抱渐进增强,同时针对延迟和性能进行优化。因此,Fresh 应用程序往往能够获得更高的 Lighthouse 分数,并且可以在互联网带宽较低的地区正常运行。
这是一个将身份验证添加到您的 Fresh 应用程序的简单指南。请按照以下步骤操作,或在此处查看源代码。
创建一个新的 Fresh 应用程序
首先,让我们创建一个新的 Fresh 应用程序。
$ deno run -A -r https://fresh.deno.dev my-auth-app
为了保持简单,让我们删除一些东西
$ rm -rf islands/Counter.tsx routes/api/joke.ts routes/\[name\].tsx
index.tsx
更新 首先,让我们更新我们的 import_map.json
以包含 std lib
{
"imports": {
"$fresh/": "https://deno.land/x/[email protected]/",
"preact": "https://esm.sh/[email protected]",
"preact/": "https://esm.sh/[email protected]/",
"preact-render-to-string": "https://esm.sh/*[email protected]",
"@preact/signals": "https://esm.sh/*@preact/[email protected]",
"@preact/signals-core": "https://esm.sh/*@preact/[email protected]",
"twind": "https://esm.sh/[email protected]",
"twind/": "https://esm.sh/[email protected]/",
"std/": "https://deno.land/[email protected]/"
}
}
接下来,让我们更新 /routes/index.tsx
以显示您的登录状态。
我们将使用 cookies(借助 std/cookie
中的 getCookies
)来检查用户的状态。
import type { Handlers, PageProps } from "$fresh/server.ts";
import { getCookies } from "std/http/cookie.ts";
interface Data {
isAllowed: boolean;
}
export const handler: Handlers = {
GET(req, ctx) {
const cookies = getCookies(req.headers);
return ctx.render!({ isAllowed: cookies.auth === "bar" });
},
};
export default function Home({ data }: PageProps<Data>) {
return (
<div>
<div>
You currently {data.isAllowed ? "are" : "are not"} logged in.
</div>
</div>
);
}
自定义处理程序只是检查 cookies 并设置 isAllowed
。我们的默认组件将根据 isAllowed
的值显示不同的状态。目前,由于我们尚未添加任何登录或设置 cookie 的逻辑,因此它显示
接下来,让我们创建一个名为 <Login>
的组件作为登录表单。
<Login>
组件
创建 我们可以在 index.tsx
中直接创建这个组件
function Login() {
return (
<form method="post" action="/api/login">
<input type="text" name="username" />
<input type="password" name="password" />
<button type="submit">Submit</button>
</form>
);
}
此组件将 POST
username
和 password
到 /api/login
,我们稍后将定义它。请注意,该表单使用 multipart/form-data
(而不是 json
),它尽可能依赖于原生浏览器功能,并最大限度地减少对任何客户端 JavaScript 的需求。
接下来,让我们在 /api/login
端点创建实际的身份验证逻辑。
login
和 logout
路由
添加 在 /routes/api/
下,让我们创建 login.ts
$ touch /routes/api/login.ts
此端点的所有身份验证逻辑都将包含在自定义处理函数中。
为简单起见,我们的用户名和密码将硬编码为 “deno” 和 “land”。(在大多数生产环境中,您将使用身份验证策略、来自持久数据存储的令牌等。)
当向 /api/login
发出 POST
请求时,自定义处理函数将执行以下操作
- 从
req
请求参数中提取username
和password
- 对照我们硬编码的用户名和密码进行检查
- 将
auth
cookie 设置为bar
(在生产环境中,这应该是每个会话的唯一值),maxAge
为 120(2 分钟后过期) - 并返回适当的 HTTP 响应 (
HTTP 303
强制方法返回到GET
,防止奇怪的浏览器历史记录行为)
这是代码
import { Handlers } from "$fresh/server.ts";
import { setCookie } from "std/http/cookie.ts";
export const handler: Handlers = {
async POST(req) {
const url = new URL(req.url);
const form = await req.formData();
if (form.get("username") === "deno" && form.get("password") === "land") {
const headers = new Headers();
setCookie(headers, {
name: "auth",
value: "bar", // this should be a unique value for each session
maxAge: 120,
sameSite: "Lax", // this is important to prevent CSRF attacks
domain: url.hostname,
path: "/",
secure: true,
});
headers.set("location", "/");
return new Response(null, {
status: 303, // "See Other"
headers,
});
} else {
return new Response(null, {
status: 403,
});
}
},
};
让我们也创建一个用于注销的端点:/routes/logout.ts
$ touch routes/logout.ts
注销逻辑将删除登录时设置的 cookie,并将用户重定向到根页面
import { Handlers } from "$fresh/server.ts";
import { deleteCookie } from "std/http/cookie.ts";
export const handler: Handlers = {
GET(req) {
const url = new URL(req.url);
const headers = new Headers(req.headers);
deleteCookie(headers, "auth", { path: "/", domain: url.hostname });
headers.set("location", "/");
return new Response(null, {
status: 302,
headers,
});
},
};
现在,让我们通过返回到 /routes/index.tsx
并添加我们的登录和注销组件,将所有内容联系起来。
<Login>
和注销添加到 index
将 在我们的 routes/index.tsx
页面的 <Home>
组件中,让我们添加 <Login>
组件,以及一个注销按钮(它向 /logout
发送请求)
// routes/index.tsx
export default function Home({ data }: PageProps<Data>) {
return (
<div>
<div>
You currently {data.isAllowed ? "are" : "are not"} logged in.
</div>
{!data.isAllowed ? <Login /> : <a href="/logout">Logout</a>}
</div>
);
}
检查 localhost,现在我们有了可以工作的登录和注销按钮
不错!
处理未登录用户
许多付费墙网站会在用户未登录时自动重定向用户。
我们可以通过在自定义处理程序中添加重定向逻辑来添加此功能
import type { Handlers } from "$fresh/server.ts";
import { getCookies } from "std/http/cookie.ts";
export default function Home() {
return (
<div>
Here is some secret
</div>
);
}
export const handler: Handlers = {
GET(req, ctx) {
const cookies = getCookies(req.headers);
if (cookies.auth === "bar") {
return ctx.render!();
} else {
const url = new URL(req.url);
url.pathname = "/";
return Response.redirect(url);
}
},
};
成功!
或者,如果您不想重定向用户,而只想在用户通过身份验证时显示秘密内容
import type { Handlers, PageProps } from "$fresh/server.ts";
import { getCookies } from "std/http/cookie.ts";
interface Data {
isAllowed: boolean;
}
export default function Home({ data }: PageProps<Data>) {
return (
<div>
{data.isAllowed ? "Here is some secret" : "You are not allowed here"}
</div>
);
}
export const handler: Handlers<Data> = {
GET(req, ctx) {
const cookies = getCookies(req.headers);
return ctx.render!({ isAllowed: cookies.auth === "bar" });
},
};
下一步是什么
这是一个将身份验证添加到您的 Fresh 应用程序的简要指南。我们计划在以后的文章中探讨许多使其更适合生产环境的方法
- 在
/routes/api/login.ts
中添加更强大的身份验证策略 - 使用唯一值使 cookie 和会话管理更加安全
- 使用持久性数据存储(如MongoDB 用于用户帐户或 Redis 用于会话管理)
我们希望这篇文章对您有所帮助!
遇到困难?在 Twitter 或 我们的 Discord 上提出关于 Fresh 和 Deno 的问题。