跳到主要内容

身份验证

配置 Next-Auth

本节代码链接

备注

具体内容可参考Authentication

Refactor NavBar

本节代码链接

/app/Navbar.tsx
/app/Navbar.tsx
"use client";
import { Avatar, Box, DropdownMenu, Flex, Text } from "@radix-ui/themes";
import classNames from "classnames";
import { useSession } from "next-auth/react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { AiFillBug } from "react-icons/ai";
import { Skeleton } from "@/app/components";

const NavBar = () => {
return (
<nav className="border-b mb-5 px-5 py-5">
<Flex align="center" justify="between">
<NavLinks />
<Avator />
</Flex>
</nav>
);
};

export default NavBar;

const links = [
{ label: <AiFillBug />, href: "/" },
{ label: "DashBoard", href: "/dashboard" },
{ label: "Issues", href: "/issues" },
];

const NavLinks = () => {
const currentPath = usePathname();

return (
<ul className="flex gap-6 items-center">
{links.map((link) => (
<li key={link.href}>
<Link
className={classNames({
"text-zinc-900": link.href === currentPath,
"text-zinc-500": link.href !== currentPath,
"hover:text-zinc-800 transaition-colors": true,
})}
href={link.href}
>
{link.label}
</Link>
</li>
))}
</ul>
);
};

const Avator = () => {
const { status, data: session } = useSession();

if (status === "loading") return <Skeleton width="3rem" />;
if (status === "unauthenticated")
return (
<Link
className="text-zinc-500 hover:text-zinc-800 transaition-colors"
href="/api/auth/signin"
>
Sign In
</Link>
);

return (
<Box>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Avatar
src={session!.user!.image!}
fallback="?"
size="2"
radius="full"
className="cursor-pointer"
referrerPolicy="no-referrer"
/>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Label>
<Text size="2">{session!.user!.email}</Text>
</DropdownMenu.Label>
<DropdownMenu.Item>
<Link href="/api/auth/signout">Sign Out</Link>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Box>
);
};

Secure the Application

本节代码链接

AuthOptions

首先,我们应该将 AuthOptions 放到一个单独的文件里备用

/app/api/auth/AuthOptions.tsx
import prisma from "@/prisma/client";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { NextAuthOptions } from "next-auth";
import Github from "next-auth/providers/github";

const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(prisma),
providers: [
Github({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
}),
],
};

export default authOptions;

保护 API 与 页面

在除 GET 外的所有 API 中都应该加上确认是否有 session 的验证

/app/api/issues/[id]/route.tsx
  ...
+ import { getServerSession } from "next-auth";
+ import authOptions from "@/app/api/auth/AuthOptions";

export async function PATCH(
request: NextRequest,
{ params }: { params: { id: string } }
) {
+ const session = await getServerSession(authOptions);
+ if (!session) return NextResponse.json({}, { status: 401 });
...
}

export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
+ const session = await getServerSession(authOptions);
+ if (!session) return NextResponse.json({}, { status: 401 })
...
}

同样,我们应该在一些页面将删除修改的组件隐藏

/app/issues/[id]/page.tsx
+ import { getServerSession } from "next-auth";
+ import authOptions from "@/app/api/auth/AuthOptions";

interface Props {
params: { id: string };
}
const IssueDeatilPage = async ({ params }: Props) => {
+ const session = await getServerSession(authOptions);

const issue = await prisma.issue.findUnique({
where: { id: parseInt(params.id) },
});

if (!issue) notFound();

return (
<Grid columns={{ initial: "1", sm: "5" }} gap="5">
<Box className="md:col-span-4">
<IssueDetails issue={issue} />
</Box>
+ {session && (
<Box>
<Flex direction="column" gap="3">
<EditIssueButton issueId={issue.id} />
<DeleteIssueButton issueId={issue.id} />
</Flex>
</Box>
)}
</Grid>
);
};
export default IssueDeatilPage;

MiddleWare

同样我们可以使用 MiddleWare 来保护路由

export { default } from "next-auth/middleware";

export const config = {
matcher: ["/issues/new", "/issues/edit/:id+"],
};
请作者喝可乐🥤: