如何使用 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 库
{
"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
以显示您的登录状态。
我们将使用 cookie(借助于来自 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>
);
}
自定义处理程序只是检查 cookie 并设置 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
(在生产环境中,这应该是一个每个会话唯一的 value)并设置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 的问题。